【发布时间】:2014-10-18 10:52:39
【问题描述】:
我一直在 In an OpenMP parallel code, would there be any benefit for memset to be run in parallel? 测试代码,我发现了一些意想不到的东西。
我的系统是单插槽 Xeon E5-1620,它是具有 4 个物理内核和 8 个超线程的 Ivy Bridge 处理器。我正在使用 Ubuntu 14.04 LTS、Linux 内核 3.13、GCC 4.9.0 和 EGLIBC 2.19。我用gcc -fopenmp -O3 mem.c编译
当我在链接中运行代码时,它默认为八个线程并给出
Touch: 11830.448 MB/s
Rewrite: 18133.428 MB/s
但是,当我绑定线程并将线程数设置为这样的物理内核数时
export OMP_NUM_THREADS=4
export OMP_PROC_BIND=true
我明白了
Touch: 22167.854 MB/s
Rewrite: 18291.134 MB/s
点击率翻倍!绑定后运行几次总是比重写快。我不明白这一点。 绑定线程并设置物理核数后为什么touch比rewrite快?为什么触摸率翻了一番?
这是我使用的代码,未经修改从 Hristo Iliev 答案中获取。
#include <stdio.h>
#include <string.h>
#include <omp.h>
void zero(char *buf, size_t size)
{
size_t my_start, my_size;
if (omp_in_parallel())
{
int id = omp_get_thread_num();
int num = omp_get_num_threads();
my_start = (id*size)/num;
my_size = ((id+1)*size)/num - my_start;
}
else
{
my_start = 0;
my_size = size;
}
memset(buf + my_start, 0, my_size);
}
int main (void)
{
char *buf;
size_t size = 1L << 31; // 2 GiB
double tmr;
buf = malloc(size);
// Touch
tmr = -omp_get_wtime();
#pragma omp parallel
{
zero(buf, size);
}
tmr += omp_get_wtime();
printf("Touch: %.3f MB/s\n", size/(1.e+6*tmr));
// Rewrite
tmr = -omp_get_wtime();
#pragma omp parallel
{
zero(buf, size);
}
tmr += omp_get_wtime();
printf("Rewrite: %.3f MB/s\n", size/(1.e+6*tmr));
free(buf);
return 0;
}
编辑: 没有胎面绑定,但在这里使用四个线程是运行八次的结果。
Touch: 14723.115 MB/s, Rewrite: 16382.292 MB/s
Touch: 14433.322 MB/s, Rewrite: 16475.091 MB/s
Touch: 14354.741 MB/s, Rewrite: 16451.255 MB/s
Touch: 21681.973 MB/s, Rewrite: 18212.101 MB/s
Touch: 21004.233 MB/s, Rewrite: 17819.072 MB/s
Touch: 20889.179 MB/s, Rewrite: 18111.317 MB/s
Touch: 14528.656 MB/s, Rewrite: 16495.861 MB/s
Touch: 20958.696 MB/s, Rewrite: 18153.072 MB/s
编辑:
我在另外两个系统上测试了这段代码,但我无法在它们上重现问题
i5-4250U (Haswell) - 2 个物理核心,4 个超线程
4 threads unbound
Touch: 5959.721 MB/s, Rewrite: 9524.160 MB/s
2 threads bound to each physical core
Touch: 7263.175 MB/s, Rewrite: 9246.911 MB/s
四个插槽 E7- 4850 - 10 个物理内核,每个插槽 20 个超线程
80 threads unbound
Touch: 10177.932 MB/s, Rewrite: 25883.520 MB/s
40 threads bound
Touch: 10254.678 MB/s, Rewrite: 30665.935 MB/s
这表明将线程绑定到物理内核确实改善了触摸和重写,但在这两个系统上触摸比重写慢。
我还测试了 memset 的三种不同变体:my_memset、my_memset_stream 和 A_memset。
函数my_memset 和my_memset_stream 定义如下。函数A_memset来自Agner Fog的asmlib。
my_memset 结果:
Touch: 22463.186 MB/s
Rewrite: 18797.297 MB/s
我认为这表明问题不在 EGLIBC 的 memset 函数中。
A_memset 结果:
Touch: 18235.732 MB/s
Rewrite: 44848.717 MB/s
my_memset_stream:
Touch: 18678.841 MB/s
Rewrite: 44627.270 MB/s
查看 asmlib 的源代码,我看到在编写大块内存时使用了非临时存储。这就是为什么my_memset_stream 的带宽与 Agner Fog 的 asmlib 大致相同。 maximum throughput of this system is 51.2 GB/s。因此,这表明 A_memset 和 my_memset_stream 获得了大约 85% 的最大吞吐量。
void my_memset(int *s, int c, size_t n) {
int i;
for(i=0; i<n/4; i++) {
s[i] = c;
}
}
void my_memset_stream(int *s, int c, size_t n) {
int i;
__m128i v = _mm_set1_epi32(c);
for(i=0; i<n/4; i+=4) {
_mm_stream_si128((__m128i*)&s[i], v);
}
}
【问题讨论】:
-
没有
OMP_PROC_BIND的4个线程呢? -
@HristoIliev,我在答案的末尾添加了八次运行,没有线程绑定但有四个线程。
-
@HristoIliev,当线程绑定在大约 22 GB/s 的触摸和 18 GB/s 的重写时,它是稳定的。但是当线程未绑定时它是不稳定的(正如您在我的问题的编辑中看到的那样)。
-
我很困惑。鉴于线程组是在第一个并行区域中创建的,这绝对没有意义。它可能与
omp_get_wtime()(最近的libgomp 版本中的CLOCK_MONOTONIC)使用的定时器源有关。尝试通过 LIKWID 或类似的分析工具运行它,看看它报告的内存速度或尝试以不同的方式测量时间。 -
同意,除了线程创建,内存页面在第一次触摸时初始化。没有理由让相同数据上相同线程上的相同代码执行得更慢。除了一些 Turbo Boost 效果?否则它看起来像一个错误
标签: multithreading parallel-processing openmp sse memset