Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/EmptyTag/151.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 从两个阵列的点积测量存储器带宽_C++_Memory_Openmp_Bandwidth_Avx - Fatal编程技术网

C++ 从两个阵列的点积测量存储器带宽

C++ 从两个阵列的点积测量存储器带宽,c++,memory,openmp,bandwidth,avx,C++,Memory,Openmp,Bandwidth,Avx,两个阵列的点积 for(int i=0; i<n; i++) { sum += x[i]*y[i]; } 有人能给我解释一下为什么一个线程的带宽超过两倍,而使用多个线程的带宽超过三倍吗? 以下是我使用的代码: //g++ -O3 -fopenmp -mavx -ffast-math dot.cpp #include <stdio.h> #include <string.h> #include <stdlib.h> #include <st

两个阵列的点积

for(int i=0; i<n; i++) {
    sum += x[i]*y[i];
}
有人能给我解释一下为什么一个线程的带宽超过两倍,而使用多个线程的带宽超过三倍吗?

以下是我使用的代码:

//g++ -O3 -fopenmp -mavx -ffast-math dot.cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <x86intrin.h>
#include <omp.h>

extern "C" inline float horizontal_add(__m256 a) {
    __m256 t1 = _mm256_hadd_ps(a,a);
    __m256 t2 = _mm256_hadd_ps(t1,t1);
    __m128 t3 = _mm256_extractf128_ps(t2,1);
    __m128 t4 = _mm_add_ss(_mm256_castps256_ps128(t2),t3);
    return _mm_cvtss_f32(t4);
}

extern "C" float dot_avx(float * __restrict x, float * __restrict y, const int n) {
    x = (float*)__builtin_assume_aligned (x, 32);
    y = (float*)__builtin_assume_aligned (y, 32);
    float sum = 0;
    #pragma omp parallel reduction(+:sum)
    {
        __m256 sum1 = _mm256_setzero_ps();
        __m256 sum2 = _mm256_setzero_ps();
        __m256 sum3 = _mm256_setzero_ps();
        __m256 sum4 = _mm256_setzero_ps();
        __m256 x8, y8;
        #pragma omp for
        for(int i=0; i<n; i+=32) {
            x8 = _mm256_loadu_ps(&x[i]);
            y8 = _mm256_loadu_ps(&y[i]);
            sum1 = _mm256_add_ps(_mm256_mul_ps(x8,y8),sum1);
            x8 = _mm256_loadu_ps(&x[i+8]);
            y8 = _mm256_loadu_ps(&y[i+8]);
            sum2 = _mm256_add_ps(_mm256_mul_ps(x8,y8),sum2);
            x8 = _mm256_loadu_ps(&x[i+16]);
            y8 = _mm256_loadu_ps(&y[i+16]);
            sum3 = _mm256_add_ps(_mm256_mul_ps(x8,y8),sum3);
            x8 = _mm256_loadu_ps(&x[i+24]);
            y8 = _mm256_loadu_ps(&y[i+24]);
            sum4 = _mm256_add_ps(_mm256_mul_ps(x8,y8),sum4);
        }
        sum += horizontal_add(_mm256_add_ps(_mm256_add_ps(sum1,sum2),_mm256_add_ps(sum3,sum4)));
    }
    return sum; 
}

extern "C" float dot(float * __restrict x, float * __restrict y, const int n) {
    x = (float*)__builtin_assume_aligned (x, 32);
    y = (float*)__builtin_assume_aligned (y, 32);
    float sum = 0;
    for(int i=0; i<n; i++) {
        sum += x[i]*y[i];
    }
    return sum;
}

int main(){
    uint64_t LEN = 1 << 27;
    float *x = (float*)_mm_malloc(sizeof(float)*LEN,64);
    float *y = (float*)_mm_malloc(sizeof(float)*LEN,64);
    for(uint64_t i=0; i<LEN; i++) { x[i] = 1.0*rand()/RAND_MAX - 0.5; y[i] = 1.0*rand()/RAND_MAX - 0.5;}

    uint64_t size = 2*sizeof(float)*LEN;

    volatile float sum = 0;
    double dtime, rate, flops;  
    int repeat = 100;

    dtime = omp_get_wtime();
    for(int i=0; i<repeat; i++) sum += dot(x,y,LEN);
    dtime = omp_get_wtime() - dtime;
    rate = 1.0*repeat*size/dtime*1E-9;
    flops = 2.0*repeat*LEN/dtime*1E-9;
    printf("%f GB, sum %f, time %f s, %.2f GB/s, %.2f GFLOPS\n", 1.0*size/1024/1024/1024, sum, dtime, rate,flops);

    sum = 0;
    dtime = omp_get_wtime();
    for(int i=0; i<repeat; i++) sum += dot_avx(x,y,LEN);
    dtime = omp_get_wtime() - dtime;
    rate = 1.0*repeat*size/dtime*1E-9;
    flops = 2.0*repeat*LEN/dtime*1E-9;

    printf("%f GB, sum %f, time %f s, %.2f GB/s, %.2f GFLOPS\n", 1.0*size/1024/1024/1024, sum, dtime, rate,flops);
}
八线

Function      Rate (MB/s)   Avg time     Min time     Max time
Copy:       24501.2282       0.0014       0.0013       0.0021
Scale:      23121.0556       0.0014       0.0014       0.0015
Add:        25263.7209       0.0024       0.0019       0.0056
Triad:      25817.7215       0.0020       0.0019       0.0027

这里有几件事可以归结为:

  • 您必须相当努力地工作,以获得内存子系统的最后一点性能;及
  • 不同的基准衡量不同的事物
第一个有助于解释为什么需要多个线程来饱和可用内存带宽。内存系统中有很多并发性,利用这些并发性通常需要CPU代码中的一些并发性。多线程执行帮助的一个重要原因是——当一个线程暂停等待数据到达时,另一个线程可能能够利用刚刚可用的其他一些数据

在这种情况下,硬件可以在单个线程上为您提供很多帮助-因为内存访问是可预测的,所以硬件可以在您需要数据时提前预取数据,从而使您即使只使用一个线程,也具有延迟隐藏的一些优势;但预回迁的功能是有限的。例如,预取程序不会自行跨越页面边界。这方面的标准参考是,现在已经足够老了,一些差距已经开始显现(英特尔对Sandy Bridge处理器的热芯片概述是-特别注意内存管理硬件与CPU的更紧密集成)

至于与memset进行比较的问题,或者说,跨基准进行比较总是会令人头痛,即使是声称衡量同一事物的基准。特别是,“内存带宽”并不是一个单一的数字——性能因操作而异。mbw和Stream都执行某种版本的复制操作,此处说明了STREAMs操作(直接从网页上获取,所有操作数都是双精度浮点):

因此,在这些情况下,大约1/2-1/3的内存操作是写操作(在memset的情况下,所有操作都是写操作)。虽然单个写操作可能会比读取操作慢一点,但更大的问题是,用写操作使内存子系统饱和要困难得多,因为您当然不能执行与预取写操作相同的操作。交替读写有帮助,但您的点积示例(本质上是所有读操作)将是将指针固定在内存带宽上的最佳情况


此外,流基准测试(有意地)是完全可移植的,只有一些编译器杂注来建议矢量化,因此击败流基准测试不一定是一个警告信号,特别是当您正在进行两次流读取时。

我自己编写了内存基准测试代码

以下是八个线程的当前结果:

write:    0.5 GB, time 2.96e-01 s, 18.11 GB/s
copy:       1 GB, time 4.50e-01 s, 23.85 GB/s
scale:      1 GB, time 4.50e-01 s, 23.85 GB/s
add:      1.5 GB, time 6.59e-01 s, 24.45 GB/s
mul:      1.5 GB, time 6.56e-01 s, 24.57 GB/s
triad:    1.5 GB, time 6.61e-01 s, 24.37 GB/s
vsum:     0.5 GB, time 1.49e-01 s, 36.09 GB/s, sum -8.986818e+03
vmul:     0.5 GB, time 9.00e-05 s, 59635.10 GB/s, sum 0.000000e+00
vmul_sum:   1 GB, time 3.25e-01 s, 33.06 GB/s, sum 1.910421e+04
以下是1个线程的当前结果:

write:    0.5 GB, time 4.65e-01 s, 11.54 GB/s
copy:       1 GB, time 7.51e-01 s, 14.30 GB/s
scale:      1 GB, time 7.45e-01 s, 14.41 GB/s
add:      1.5 GB, time 1.02e+00 s, 15.80 GB/s
mul:      1.5 GB, time 1.07e+00 s, 15.08 GB/s
triad:    1.5 GB, time 1.02e+00 s, 15.76 GB/s
vsum:     0.5 GB, time 2.78e-01 s, 19.29 GB/s, sum -8.990941e+03
vmul:     0.5 GB, time 1.15e-05 s, 468719.08 GB/s, sum 0.000000e+00
vmul_sum:   1 GB, time 5.72e-01 s, 18.78 GB/s, sum 1.910549e+04
  • 写入:将常量(3.14159)写入数组。这应该类似于
    memset
  • 复制、缩放、添加和空间坐标轴的定义与流中的定义相同
  • mul:
    a(i)=b(i)*c(i)
  • vsum:
    sum+=a(i)
  • vmul:
    sum*=a(i)
  • vmul_sum:
    sum+=a(i)*b(i)
    //点积

  • 我的结果与STREAM一致。我为
    vsum
    获得了最高带宽。
    vmul
    方法当前不起作用(一旦该值为零,它将提前完成)。使用intrinsic和展开我稍后添加的循环,我可以获得稍微好一点的结果(大约10%)

    您有多少个物理CPU?你的内存通道是如何填充的?我希望你在某个时候写下整个项目。在这里,问题只是一个线程没有完全饱和内存子系统——这与说单线程的性能还有改进的余地并不一定相同。通过预取,并且同时有多个内存请求,可能有一些操作数已经准备好进行点生产,但它们不是第一个线程所期望的操作数。你可能已经看过了——它现在有点老了,但很全面。@JonathanDursi,我想我需要读一读“每个程序员都应该知道的关于内存的知识”。过去我试过几次,但总共有114页……我将试着把这段对话中的一些内容提炼成一个答案……我还发现,内存带宽更难预测和测量。首先,在读和写带宽之间有一个明显的区别。在某些系统上,由于它们使用不同的通道,因此可以在这两个系统上获得全部带宽。那么你是否流也很重要。如果不流式写入,它们也会产生读取成本。与缓存和其他内部CPU瓶颈不同,带宽需求的增加不会导致性能曲线出现“悬崖峭壁”。你看到的是平滑递减的回报。我想我现在有了自己的基准:点积:-)我必须承认,我很惊讶多线程在这种情况下有帮助。我在过去曾多次观察到这一点,但不相信结果,因为它与我对CPU工作方式的天真看法相冲突。我假设CPU正在等待数据,而另一个CPU不会有帮助。但是,如果一个CPU正在等待一组特定的数据(而不是任何数据集),而另一个CPU正在等待另一组特定的数据,那么我可以理解多个线程如何提供帮助。我发布了一些结果来回答我的问题。通过绑定线程(
    export-OMP\u-PROC\u-BIND=true
    )和将线程数设置为物理核心数(即不使用超线程),例如vsum变为nearl,我得到了更好的结果
    ------------------------------------------------------------------
    name        kernel                  bytes/iter      FLOPS/iter
    ------------------------------------------------------------------
    COPY:       a(i) = b(i)                 16              0
    SCALE:      a(i) = q*b(i)               16              1
    SUM:        a(i) = b(i) + c(i)          24              1
    TRIAD:      a(i) = b(i) + q*c(i)        24              2
    ------------------------------------------------------------------
    
    write:    0.5 GB, time 2.96e-01 s, 18.11 GB/s
    copy:       1 GB, time 4.50e-01 s, 23.85 GB/s
    scale:      1 GB, time 4.50e-01 s, 23.85 GB/s
    add:      1.5 GB, time 6.59e-01 s, 24.45 GB/s
    mul:      1.5 GB, time 6.56e-01 s, 24.57 GB/s
    triad:    1.5 GB, time 6.61e-01 s, 24.37 GB/s
    vsum:     0.5 GB, time 1.49e-01 s, 36.09 GB/s, sum -8.986818e+03
    vmul:     0.5 GB, time 9.00e-05 s, 59635.10 GB/s, sum 0.000000e+00
    vmul_sum:   1 GB, time 3.25e-01 s, 33.06 GB/s, sum 1.910421e+04
    
    write:    0.5 GB, time 4.65e-01 s, 11.54 GB/s
    copy:       1 GB, time 7.51e-01 s, 14.30 GB/s
    scale:      1 GB, time 7.45e-01 s, 14.41 GB/s
    add:      1.5 GB, time 1.02e+00 s, 15.80 GB/s
    mul:      1.5 GB, time 1.07e+00 s, 15.08 GB/s
    triad:    1.5 GB, time 1.02e+00 s, 15.76 GB/s
    vsum:     0.5 GB, time 2.78e-01 s, 19.29 GB/s, sum -8.990941e+03
    vmul:     0.5 GB, time 1.15e-05 s, 468719.08 GB/s, sum 0.000000e+00
    vmul_sum:   1 GB, time 5.72e-01 s, 18.78 GB/s, sum 1.910549e+04