memset与绑定到每个物理核心的线程并行

Z b*_*son 7 parallel-processing multithreading sse openmp memset

我一直在测试一个OpenMP并行代码中的代码,memset并行运行会有什么好处吗?我正在观察一些意外的事情.

我的系统是一个单插槽Xeon E5-1620,它是一个Ivy Bridge处理器,有4个物理内核和8个超线程.我使用的是Ubuntu 14.04 LTS,Linux Kernel 3.13,GCC 4.9.0和EGLIBC 2.19.我编译gcc -fopenmp -O3 mem.c

当我在链接中运行代码时,它默认为八个线程并给出

Touch:   11830.448 MB/s
Rewrite: 18133.428 MB/s
Run Code Online (Sandbox Code Playgroud)

但是,当我绑定线程并将线程数设置为这样的物理核心数

export OMP_NUM_THREADS=4 
export OMP_PROC_BIND=true
Run Code Online (Sandbox Code Playgroud)

我明白了

Touch:   22167.854 MB/s
Rewrite: 18291.134 MB/s
Run Code Online (Sandbox Code Playgroud)

触控率增加了一倍!绑定后运行几次总是比重写更快.我不明白这一点.绑定线程并将其设置为物理核心数后,为什么触摸比重写更快?为什么触控率翻倍?

这是我使用的代码,没有修改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;
}
Run Code Online (Sandbox Code Playgroud)

编辑:没有胎面装订,但在这里使用四个线程的结果是运行八次.

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
Run Code Online (Sandbox Code Playgroud)

编辑:

我在其他两个系统上测试了这个代码,我无法重现它们的问题

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
Run Code Online (Sandbox Code Playgroud)

四个插座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
Run Code Online (Sandbox Code Playgroud)

这表明将线程绑定到物理内核确实改善了触摸和重写,但触摸比在这两个系统上重写要慢.

我还测试了memset的三种不同的变化:my_memset,my_memset_stream,和A_memset.功能my_memsetmy_memset_stream定义如下.该功能A_memset来自Agner Fog的asmlib.

my_memset结果:

Touch:   22463.186 MB/s
Rewrite: 18797.297 MB/s
Run Code Online (Sandbox Code Playgroud)

我认为这表明问题不在EGLIBC的memset函数中.

A_memset结果:

Touch:   18235.732 MB/s
Rewrite: 44848.717 MB/s
Run Code Online (Sandbox Code Playgroud)

my_memset_stream:

Touch:   18678.841 MB/s
Rewrite: 44627.270 MB/s
Run Code Online (Sandbox Code Playgroud)

看一下asmlib的源代码,我看到了用于编写非时间存储的大块内存.这就是为什么my_memset_stream得到与Agner Fog的asmlib相同的带宽.该系统最大吞吐量为51.2 GB/s.所以这表明A_memsetmy_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);
    }
}
Run Code Online (Sandbox Code Playgroud)

小智 0

从您的数字来看,您的 4 个绑定线程正在 2 个物理核心上运行,而不是预期的 4 个物理核心上。你能否证实这一点?这可以解释触摸时间加倍的原因。我不确定在系统上使用超线程时如何强制线程进入物理核心。{我尝试将其添加为问题,但“声誉”不足}