gpt4 book ai didi

c++ - 为什么 AVX 点积比原生 C++ 代码慢

转载 作者:行者123 更新时间:2023-11-30 01:39:05 25 4
gpt4 key购买 nike

我有以下 AVX 和 native 代码:

__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 位对齐。(D3 与 __declspec... 和 aVx arr 与 _mm_malloc()..)而且,正如我所见, native 变体与 AVX 变体相等/或更快。我不明白这是正常行为?因为我认为 AVX 是“超快”的……如果不是,我该如何优化它?我使用 arch AVX 在 MSVC 2015(x64) 上编译它。另外,我的硬件是 intel i7 4750HQ(haswell)

最佳答案

使用基本循环进行简单的分析并不是一个好主意 - 它通常只意味着内存带宽有限,因此测试最终以大致相同的速度结束 (内存通常比 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);
}

汇编:

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 现在可用!)。最近出现了一个与此非常相似的问题:For C++ Vector3 utility class implementations, is array faster than struct and 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))));
}

汇编:

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 个),您将开始理解为什么这是要走的路......

关于c++ - 为什么 AVX 点积比原生 C++ 代码慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46502748/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com