繁体   English   中英

为什么AVX点产品比本地C ++代码慢

[英]Why AVX dot product slower than native C++ code

我有以下AVX和本机代码:

__forceinline double dotProduct_2(const double* u, const double* v)   
{  
    _mm256_zeroupper();   
    __m256d xy          = _mm256_mul_pd(_mm256_load_pd(u), _mm256_load_pd(v));
    __m256d temp        = _mm256_hadd_pd(xy, xy);
    __m128d dotproduct  = _mm_add_pd(_mm256_extractf128_pd(temp, 0), _mm256_extractf128_pd(temp, 1));
    return dotproduct.m128d_f64[0];
}

__forceinline double dotProduct_1(const D3& a, const D3& b)
{
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
}

以及相应的测试脚本:

std::cout << res_1 << " " << res_2 << " " << res_3 << '\n';
{
    std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();

    for (int i = 0; i < (1 << 30); ++i)
    {
        zx_1 += dotProduct_1(aVx[i % 10000], aVx[(i + 1) % 10000]);
    }
    std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();

    std::cout << "NAIVE : " << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() << '\n';
}

{
    std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < (1 << 30); ++i)
    {
        zx_2 += dotProduct_2(&aVx[i % 10000][0], &aVx[(i + 1) % 10000][0]);
    }

    std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();

    std::cout << "AVX : " << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() << '\n';
}

std::cout << math::min2(zx_1, zx_2) << " " << zx_1 << " " << zx_2;

好吧,所有数据都以32对齐。(带有__declspec ...的D3和带有_mm_malloc()的aVx arr。)而且,正如我所看到的,本机变量等于/或比AVX变量更快。 我不明白这是正常行为吗? 因为我认为AVX是“超级快速” ...如果没有,我如何对其进行优化? 我使用Arch AVX在MSVC 2015(x64)上进行编译。 另外,我的硬汉是Intel i7 4750HQ(haswell)

vzeroupper和hadd指令会增加过多开销。 编写它的好方法是在循环中进行所有乘法,并在最后只汇总一次结果。 想象一下,您展开原始循环4次并使用4个累加器:

for(i=0; i < (1<<30); i+=4) {
  s0 += a[i+0] * b[i+0];
  s1 += a[i+1] * b[i+1];
  s2 += a[i+2] * b[i+2];
  s3 += a[i+3] * b[i+3];
}
return s0+s1+s2+s3;

现在,只需将展开的循环替换为SIMD mul并添加(甚至可用FMA固有)

使用基本循环进行简单配置不是一个好主意-它通常仅表示您受内存带宽限制,因此测试最终以大约相同的速度进行(内存通常比CPU慢,这基本上就是您要测试的全部内容)在这里)

就像其他人所说的那样,您的代码示例不是很好 ,因为您一直在穿越通道(我认为这只是为了找到最快的点积,而不是因为所有点积的总和才是理想的结果? ) 老实说,如果您真的需要一个快速的点积(用于此处显示的AOS数据),我想我更喜欢用VADDPD + VPERMILPD替换VHADDPD (交易了一条额外的指令,使吞吐量提高了一倍,并降低了延迟)

double dotProduct_3(const double* u, const double* v)   
{  
    __m256d dp = _mm256_mul_pd(_mm256_load_pd(u), _mm256_load_pd(v));
    __m128d a = _mm256_extractf128_pd(dp, 0);
    __m128d b = _mm256_extractf128_pd(dp, 1);
    __m128d c = _mm_add_pd(a, b);
    __m128d yy = _mm_unpackhi_pd(c, c);
    __m128d dotproduct  = _mm_add_pd(c, yy);
    return _mm_cvtsd_f64(dotproduct);
}

asm:

dotProduct_3(double const*, double const*):
 vmovapd ymm0,YMMWORD PTR [rsi]
 vmulpd ymm0,ymm0,YMMWORD PTR [rdi]
 vextractf128 xmm1,ymm0,0x1
 vaddpd xmm0,xmm1,xmm0
 vpermilpd xmm1,xmm0,0x3
 vaddpd xmm0,xmm1,xmm0
 vzeroupper 
 ret   

一般来说,如果您使用的是水平相加,那就错了! 尽管256位寄存器对于Vector4d来说似乎是理想的选择,但实际上并不是特别出色的表示形式(尤其是如果您认为AVX512现在可用!) 最近出现了一个非常类似的问题: 对于C ++ Vector3实用程序类的实现,数组比struct和class快吗?

如果您想要性能,那么数组结构是最好的选择。

struct HybridVec4SOA
{
  __m256d x;
  __m256d y;
  __m256d z;
  __m256d w;
};
__m256d dot(const HybridVec4SOA& a, const HybridVec4SOA& b)
{
  return _mm256_fmadd_pd(a.w, b.w, 
         _mm256_fmadd_pd(a.z, b.z, 
         _mm256_fmadd_pd(a.y, b.y, 
         _mm256_mul_pd(a.x, b.x))));
}

asm:

dot(HybridVec4SOA const&, HybridVec4SOA const&):
 vmovapd ymm1,YMMWORD PTR [rdi+0x20]
 vmovapd ymm2,YMMWORD PTR [rdi+0x40]
 vmovapd ymm3,YMMWORD PTR [rdi+0x60]
 vmovapd ymm0,YMMWORD PTR [rsi]
 vmulpd ymm0,ymm0,YMMWORD PTR [rdi]
 vfmadd231pd ymm0,ymm1,YMMWORD PTR [rsi+0x20]
 vfmadd231pd ymm0,ymm2,YMMWORD PTR [rsi+0x40]
 vfmadd231pd ymm0,ymm3,YMMWORD PTR [rsi+0x60]
 ret    

如果您比较load / mul / fmadd与hadd和extract的延迟(更重要的是吞吐量),然后考虑SOA版本一次计算4个点产品(而不是1),您将开始理解为什么要走呢?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM