Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/153.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++ 使用常春藤桥和Haswell展开环路以实现最大吞吐量_C++_X86_Intel_Sse_Avx - Fatal编程技术网

C++ 使用常春藤桥和Haswell展开环路以实现最大吞吐量

C++ 使用常春藤桥和Haswell展开环路以实现最大吞吐量,c++,x86,intel,sse,avx,C++,X86,Intel,Sse,Avx,我用AVX一次计算八个点积。在我当前的代码中,我执行以下操作(展开之前): 常春藤桥/沙桥 __m256 areg0 = _mm256_set1_ps(a[m]); for(int i=0; i<n; i++) { __m256 breg0 = _mm256_load_ps(&b[8*i]); tmp0 = _mm256_add_ps(_mm256_mul_ps(arge0,breg0), tmp0); } 因此,通过在九个时钟周期后使用8个部分

我用AVX一次计算八个点积。在我当前的代码中,我执行以下操作(展开之前):

常春藤桥/沙桥

__m256 areg0 = _mm256_set1_ps(a[m]);
for(int i=0; i<n; i++) {        
    __m256 breg0 = _mm256_load_ps(&b[8*i]);
    tmp0 = _mm256_add_ps(_mm256_mul_ps(arge0,breg0), tmp0); 
}
因此,通过在九个时钟周期后使用8个部分和(四个来自加载,五个来自乘法),我可以在每个时钟周期提交一个AVX加载,一个AVX加法和一个AVX乘法

我想这意味着在Ivy Bridge和Haswell的32位模式下不可能实现此任务的最大吞吐量,因为32位模式只有八个AVX寄存器?

编辑:关于赏金。我的主要问题仍然有效。我想获得上述Ivy Bridge或Haswell函数的最大吞吐量,
n
可以是大于或等于64的任何值。我认为这只能通过展开来实现(常春藤桥8次,哈斯韦尔10次)。如果你认为这可以用另一种方法来实现,那么让我们来看看。从某种意义上说,这是一种变化。但我不是只寻找乘法和加法,而是寻找一个256位加载(或两个128位加载)、一个AVX乘法和一个AVX加法,每个时钟周期使用常春藤桥,或者每个时钟周期使用两个256位加载和两个FMA3指令

我还想知道需要多少登记册。对于常春藤桥,我想是10。一个用于广播,一个用于加载(由于寄存器重命名仅一个),八个用于八个部分和。因此,我不认为这可以在32位模式下完成(事实上,当我在32位模式下运行时,性能会显著下降)

我应该指出,编译器可能会给出误导性的结果

下面是我用于常春藤桥的当前函数。这基本上是将64x64矩阵
a
的一行与所有64x64矩阵
b
相乘(我在
a
的每行上运行此函数64次,以获得矩阵
c
中的完整矩阵相乘)

#包括
外部“C”无效行_m64x64(常量浮点*a、常量浮点*b、浮点*C){
const int vec_size=8;
常数int n=64;
__m256 tmp0、tmp1、tmp2、tmp3、tmp4、tmp5、tmp6、tmp7;
tmp0=_mm256_loadu_ps(&c[0*vec_size]);
tmp1=_mm256_loadu_ps(&c[1*vec_size]);
tmp2=_mm256_loadu_ps(&c[2*vec_size]);
tmp3=_mm256_loadu_ps(&c[3*vec_size]);
tmp4=_mm256_loadu_ps(&c[4*vec_size]);
tmp5=_mm256_loadu_ps(&c[5*vec_size]);
tmp6=_mm256_loadu_ps(&c[6*vec_size]);
tmp7=_mm256_loadu_ps(&c[7*vec_size]);

对于(int i=0;i对于桑迪/常春藤桥,您需要在3:

  • 只有FP Add依赖于循环的上一次迭代
  • FP Add可以在每个周期发布
  • FP Add需要三个周期才能完成
  • 因此,按3/1=3展开完全隐藏了延迟
  • FP Mul和FP Load不依赖于上一次迭代,您可以依靠OoO核心以接近最优的顺序发布它们。这些指令只有在降低FP Add的吞吐量时才会影响展开因子(这里不是这种情况,FP Load+FP Add+FP Mul可以在每个周期发布)
对于Haswell,您需要在10之前展开:

  • 只有FMA依赖于循环的上一次迭代
  • FMA可以在每个周期内双倍发出指令(即独立指令平均需要0.5个周期)
  • FMA的延迟为5秒
  • 因此,按5/0.5=10展开完全隐藏了FMA延迟
  • 两个FP Load微操作不依赖于上一次迭代,并且可以与2x FMA共同发布,因此它们不会影响展开因子

    • 我在这里回答我自己的问题只是为了补充信息

      我继续分析了常春藤桥代码。当我第一次在MSVC2012中测试时,两个以上的展开没有多大帮助。但是,我怀疑MSVC没有根据我在的观察以最佳方式实现内部函数。因此,我在GCC中用
      g++-c-mavx-O3-mabi=ms
      编译内核,将对象转换为COFF64我把它丢进了MSVC,现在我知道三次展开会得到最好的结果,证实了马拉特·邓汗的答案

      以下是以秒为单位的时间,Xeon E5 1620@3.6GHz MSVC2012

      unroll    time default            time with GCC kernel
           1    3.7                     3.2
           2    1.8 (2.0x faster)       1.6 (2.0x faster)
           3    1.6 (2.3x faster)       1.2 (2.7x faster)
           4    1.6 (2.3x faster)       1.2 (2.7x faster)
      
      以下是在Linux中使用带有GCC的fma的i5-4250U的时间(
      g++-mavx-mfma-fopenmp-O3 main.cpp kernel\u fma.cpp-o sum\u fma

      以下代码适用于沙桥/常春藤桥。对于Haswell,请使用例如
      tmp0=\u mm256\u fmadd\u ps(a8,b8\u 1,tmp0)

      kernel.cpp

      #include <immintrin.h>
      
      extern "C" void foo_unroll1(const int n, const float *b, float *c) {      
          __m256 tmp0 = _mm256_set1_ps(0.0f);
          __m256 a8 = _mm256_set1_ps(1.0f);
          for(int i=0; i<n; i+=8) {
              __m256 b8 = _mm256_loadu_ps(&b[i + 0]);
              tmp0 = _mm256_add_ps(_mm256_mul_ps(a8,b8), tmp0);
          }
          _mm256_storeu_ps(c, tmp0);
      }
      
      extern "C" void foo_unroll2(const int n, const float *b, float *c) {
          __m256 tmp0 = _mm256_set1_ps(0.0f);
          __m256 tmp1 = _mm256_set1_ps(0.0f);
          __m256 a8 = _mm256_set1_ps(1.0f);
          for(int i=0; i<n; i+=16) {
              __m256 b8_1 = _mm256_loadu_ps(&b[i + 0]);
              tmp0 = _mm256_add_ps(_mm256_mul_ps(a8,b8_1), tmp0);
              __m256 b8_2 = _mm256_loadu_ps(&b[i + 8]);
              tmp1 = _mm256_add_ps(_mm256_mul_ps(a8,b8_2), tmp1);
          }
          tmp0 = _mm256_add_ps(tmp0,tmp1);
          _mm256_storeu_ps(c, tmp0);
      }
      
      extern "C" void foo_unroll3(const int n, const float *b, float *c) { 
          __m256 tmp0 = _mm256_set1_ps(0.0f);
          __m256 tmp1 = _mm256_set1_ps(0.0f);
          __m256 tmp2 = _mm256_set1_ps(0.0f);
          __m256 a8 = _mm256_set1_ps(1.0f);
          for(int i=0; i<n; i+=24) {
              __m256 b8_1 = _mm256_loadu_ps(&b[i + 0]);
              tmp0 = _mm256_add_ps(_mm256_mul_ps(a8,b8_1), tmp0);
              __m256 b8_2 = _mm256_loadu_ps(&b[i + 8]);
              tmp1 = _mm256_add_ps(_mm256_mul_ps(a8,b8_2), tmp1);
              __m256 b8_3 = _mm256_loadu_ps(&b[i + 16]);
              tmp2 = _mm256_add_ps(_mm256_mul_ps(a8,b8_3), tmp2);
          }
          tmp0 = _mm256_add_ps(tmp0,_mm256_add_ps(tmp1,tmp2));
          _mm256_storeu_ps(c, tmp0);
      }
      
      extern "C" void foo_unroll4(const int n, const float *b, float *c) {      
          __m256 tmp0 = _mm256_set1_ps(0.0f);
          __m256 tmp1 = _mm256_set1_ps(0.0f);
          __m256 tmp2 = _mm256_set1_ps(0.0f);
          __m256 tmp3 = _mm256_set1_ps(0.0f);
          __m256 a8 = _mm256_set1_ps(1.0f);
          for(int i=0; i<n; i+=32) {
              __m256 b8_1 = _mm256_loadu_ps(&b[i + 0]);
              tmp0 = _mm256_add_ps(_mm256_mul_ps(a8,b8_1), tmp0);
              __m256 b8_2 = _mm256_loadu_ps(&b[i + 8]);
              tmp1 = _mm256_add_ps(_mm256_mul_ps(a8,b8_2), tmp1);
              __m256 b8_3 = _mm256_loadu_ps(&b[i + 16]);
              tmp2 = _mm256_add_ps(_mm256_mul_ps(a8,b8_3), tmp2);
              __m256 b8_4 = _mm256_loadu_ps(&b[i + 24]);
              tmp3 = _mm256_add_ps(_mm256_mul_ps(a8,b8_4), tmp3);
          }
          tmp0 = _mm256_add_ps(_mm256_add_ps(tmp0,tmp1),_mm256_add_ps(tmp2,tmp3));
          _mm256_storeu_ps(c, tmp0);
      }
      
      #包括
      外部“C”void foo_unroll1(常量int n,常量float*b,float*C){
      __m256 tmp0=_mm256_set1_ps(0.0f);
      __m256 a8=_mm256_set1_ps(1.0f);
      
      对于(int i=0;iBe意识到展开小循环可能会对Core i7及更高版本的性能产生负面影响。循环流检测器只能在Nehalem上缓存28µOp-我不确定在Ivy Bridge/Haswell中该大小是否增加。展开的目的是什么,避免容易预测的分支?OOO能够执行在多次迭代中,您仍然应该仅受限于
      tmp0
      依赖关系。他不是说uop缓存,循环流检测器更小(并且在带宽方面更有效)我还质疑展开长延迟操作(如FMA)的依赖链的必要性。静态准备索引的好处可以忽略不计,您的性能受向量单元带宽/延迟的影响-计算索引(并预测某些分支)可以并行完成,没有影响。但只有分析才能判断谁是对的。这只适用于Sandy/Ivy Bridge。Haswell可以完成两个256位加载/周期。我指的是您最初的循环,其中(我认为)每个FMA都有一个广播和一个负载。我明白逻辑。我不清楚OoO逻辑是如何处理这个问题的,但我知道是什么
      #include <immintrin.h>
      extern "C" void row_m64x64(const float *a, const float *b, float *c) {      
          const int vec_size = 8;
          const int n = 64;
          __m256 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
          tmp0 = _mm256_loadu_ps(&c[0*vec_size]);
          tmp1 = _mm256_loadu_ps(&c[1*vec_size]);
          tmp2 = _mm256_loadu_ps(&c[2*vec_size]);
          tmp3 = _mm256_loadu_ps(&c[3*vec_size]);
          tmp4 = _mm256_loadu_ps(&c[4*vec_size]);
          tmp5 = _mm256_loadu_ps(&c[5*vec_size]);
          tmp6 = _mm256_loadu_ps(&c[6*vec_size]);
          tmp7 = _mm256_loadu_ps(&c[7*vec_size]);
      
          for(int i=0; i<n; i++) {
              __m256 areg0 = _mm256_set1_ps(a[i]);
      
              __m256 breg0 = _mm256_loadu_ps(&b[vec_size*(8*i + 0)]);
              tmp0 = _mm256_add_ps(_mm256_mul_ps(areg0,breg0), tmp0);    
              __m256 breg1 = _mm256_loadu_ps(&b[vec_size*(8*i + 1)]);
              tmp1 = _mm256_add_ps(_mm256_mul_ps(areg0,breg1), tmp1);
              __m256 breg2 = _mm256_loadu_ps(&b[vec_size*(8*i + 2)]);
              tmp2 = _mm256_add_ps(_mm256_mul_ps(areg0,breg2), tmp2);    
              __m256 breg3 = _mm256_loadu_ps(&b[vec_size*(8*i + 3)]);
              tmp3 = _mm256_add_ps(_mm256_mul_ps(areg0,breg3), tmp3);   
              __m256 breg4 = _mm256_loadu_ps(&b[vec_size*(8*i + 4)]);
              tmp4 = _mm256_add_ps(_mm256_mul_ps(areg0,breg4), tmp4);    
              __m256 breg5 = _mm256_loadu_ps(&b[vec_size*(8*i + 5)]);
              tmp5 = _mm256_add_ps(_mm256_mul_ps(areg0,breg5), tmp5);    
              __m256 breg6 = _mm256_loadu_ps(&b[vec_size*(8*i + 6)]);
              tmp6 = _mm256_add_ps(_mm256_mul_ps(areg0,breg6), tmp6);    
              __m256 breg7 = _mm256_loadu_ps(&b[vec_size*(8*i + 7)]);
              tmp7 = _mm256_add_ps(_mm256_mul_ps(areg0,breg7), tmp7);    
          }
          _mm256_storeu_ps(&c[0*vec_size], tmp0);
          _mm256_storeu_ps(&c[1*vec_size], tmp1);
          _mm256_storeu_ps(&c[2*vec_size], tmp2);
          _mm256_storeu_ps(&c[3*vec_size], tmp3);
          _mm256_storeu_ps(&c[4*vec_size], tmp4);
          _mm256_storeu_ps(&c[5*vec_size], tmp5);
          _mm256_storeu_ps(&c[6*vec_size], tmp6);
          _mm256_storeu_ps(&c[7*vec_size], tmp7);
      }
      
      unroll    time default            time with GCC kernel
           1    3.7                     3.2
           2    1.8 (2.0x faster)       1.6 (2.0x faster)
           3    1.6 (2.3x faster)       1.2 (2.7x faster)
           4    1.6 (2.3x faster)       1.2 (2.7x faster)
      
      unroll    time
           1    20.3
           2    10.2 (2.0x faster)
           3     6.7 (3.0x faster) 
           4     5.2 (4.0x faster)
           8     2.9 (7.0x faster)
          10     2.6 (7.8x faster)
      
      #include <immintrin.h>
      
      extern "C" void foo_unroll1(const int n, const float *b, float *c) {      
          __m256 tmp0 = _mm256_set1_ps(0.0f);
          __m256 a8 = _mm256_set1_ps(1.0f);
          for(int i=0; i<n; i+=8) {
              __m256 b8 = _mm256_loadu_ps(&b[i + 0]);
              tmp0 = _mm256_add_ps(_mm256_mul_ps(a8,b8), tmp0);
          }
          _mm256_storeu_ps(c, tmp0);
      }
      
      extern "C" void foo_unroll2(const int n, const float *b, float *c) {
          __m256 tmp0 = _mm256_set1_ps(0.0f);
          __m256 tmp1 = _mm256_set1_ps(0.0f);
          __m256 a8 = _mm256_set1_ps(1.0f);
          for(int i=0; i<n; i+=16) {
              __m256 b8_1 = _mm256_loadu_ps(&b[i + 0]);
              tmp0 = _mm256_add_ps(_mm256_mul_ps(a8,b8_1), tmp0);
              __m256 b8_2 = _mm256_loadu_ps(&b[i + 8]);
              tmp1 = _mm256_add_ps(_mm256_mul_ps(a8,b8_2), tmp1);
          }
          tmp0 = _mm256_add_ps(tmp0,tmp1);
          _mm256_storeu_ps(c, tmp0);
      }
      
      extern "C" void foo_unroll3(const int n, const float *b, float *c) { 
          __m256 tmp0 = _mm256_set1_ps(0.0f);
          __m256 tmp1 = _mm256_set1_ps(0.0f);
          __m256 tmp2 = _mm256_set1_ps(0.0f);
          __m256 a8 = _mm256_set1_ps(1.0f);
          for(int i=0; i<n; i+=24) {
              __m256 b8_1 = _mm256_loadu_ps(&b[i + 0]);
              tmp0 = _mm256_add_ps(_mm256_mul_ps(a8,b8_1), tmp0);
              __m256 b8_2 = _mm256_loadu_ps(&b[i + 8]);
              tmp1 = _mm256_add_ps(_mm256_mul_ps(a8,b8_2), tmp1);
              __m256 b8_3 = _mm256_loadu_ps(&b[i + 16]);
              tmp2 = _mm256_add_ps(_mm256_mul_ps(a8,b8_3), tmp2);
          }
          tmp0 = _mm256_add_ps(tmp0,_mm256_add_ps(tmp1,tmp2));
          _mm256_storeu_ps(c, tmp0);
      }
      
      extern "C" void foo_unroll4(const int n, const float *b, float *c) {      
          __m256 tmp0 = _mm256_set1_ps(0.0f);
          __m256 tmp1 = _mm256_set1_ps(0.0f);
          __m256 tmp2 = _mm256_set1_ps(0.0f);
          __m256 tmp3 = _mm256_set1_ps(0.0f);
          __m256 a8 = _mm256_set1_ps(1.0f);
          for(int i=0; i<n; i+=32) {
              __m256 b8_1 = _mm256_loadu_ps(&b[i + 0]);
              tmp0 = _mm256_add_ps(_mm256_mul_ps(a8,b8_1), tmp0);
              __m256 b8_2 = _mm256_loadu_ps(&b[i + 8]);
              tmp1 = _mm256_add_ps(_mm256_mul_ps(a8,b8_2), tmp1);
              __m256 b8_3 = _mm256_loadu_ps(&b[i + 16]);
              tmp2 = _mm256_add_ps(_mm256_mul_ps(a8,b8_3), tmp2);
              __m256 b8_4 = _mm256_loadu_ps(&b[i + 24]);
              tmp3 = _mm256_add_ps(_mm256_mul_ps(a8,b8_4), tmp3);
          }
          tmp0 = _mm256_add_ps(_mm256_add_ps(tmp0,tmp1),_mm256_add_ps(tmp2,tmp3));
          _mm256_storeu_ps(c, tmp0);
      }
      
      #include <stdio.h>
      #include <omp.h>
      #include <immintrin.h>
      
      extern "C" void foo_unroll1(const int n, const float *b, float *c);
      extern "C" void foo_unroll2(const int n, const float *b, float *c);
      extern "C" void foo_unroll3(const int n, const float *b, float *c);
      extern "C" void foo_unroll4(const int n, const float *b, float *c);
      
      int main() {
          const int n = 3*1<<10;
          const int r = 10000000;
          double dtime;
          float *b = (float*)_mm_malloc(sizeof(float)*n, 64);
          float *c = (float*)_mm_malloc(8, 64);
          for(int i=0; i<n; i++) b[i] = 1.0f;
      
          __m256 out;
          dtime = omp_get_wtime();    
          for(int i=0; i<r; i++) foo_unroll1(n, b, c);
          dtime = omp_get_wtime() - dtime;
          printf("%f, ", dtime); for(int i=0; i<8; i++) printf("%f ", c[i]); printf("\n");
      
          dtime = omp_get_wtime();    
          for(int i=0; i<r; i++) foo_unroll2(n, b, c);
          dtime = omp_get_wtime() - dtime;
          printf("%f, ", dtime); for(int i=0; i<8; i++) printf("%f ", c[i]); printf("\n");
      
          dtime = omp_get_wtime();    
          for(int i=0; i<r; i++) foo_unroll3(n, b, c);
          dtime = omp_get_wtime() - dtime;
          printf("%f, ", dtime); for(int i=0; i<8; i++) printf("%f ", c[i]); printf("\n");
      
          dtime = omp_get_wtime();    
          for(int i=0; i<r; i++) foo_unroll4(n, b, c);
          dtime = omp_get_wtime() - dtime;
          printf("%f, ", dtime); for(int i=0; i<8; i++) printf("%f ", c[i]); printf("\n");
      }