为什么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。(d3与__declspec ...和avx arr一起使用_mm_malloc()..)而且,正如我所看到的,本机变体比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);
}
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 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
与HADD和DERWERT相比,如果您比较了LOAD/MUL/FMADD的潜伏期(更重要的是吞吐量),然后考虑SOA版本一次计算4个点产品(而不是1个),您将开始理解为什么要走...
您使用vzeroupper和hadd说明添加了太多的开销。编写它的好方法是在循环中进行所有乘以所有乘以,然后在最后一次汇总结果。想象一下您将原始循环四次展开,然后使用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)
- 在nodejs中使用本机代码的最佳方法是什么?
- Winapi - SetWindowLongPtr in ShutdownBlockReason创建/销毁JNI本机代码
- 可以从本机代码(如C++/Delphi)中使用.netCore DLL
- 使用 x86_64-w64-mingw32-g++ 编译 JNI C++本机代码
- 终止调用本机代码的 .Net 线程
- 从本机代码返回到托管代码会损坏返回的对象
- 我能否通过 JNI 调试在本机代码中创建的 JVM?
- Android 无法从本机代码调用 Java 方法 JNI
- 将自定义 Java 数据模型传递给我的本机代码
- 用于 JUnit 测试的本机代码未使用 'nar-maven-plugin' 编译
- 将本机C 代码转换为Java
- 有什么方法可以用本机C 代码替换Windows API计时器(settimer)
- 使用 Android Studio 使用本机代码编译 apk 时,如何在链接处删除 libgnustl_static.
- 如何在 Linux 上调试本机代码时自动附加到多个子进程
- 如何将 WebAssembly 编译成常规汇编/本机代码(或 Cpp 等)
- Android- AAR库的调试本机代码
- 附加到 C++/CLI dll 的性能探查器无法访问本机C++代码
- AccessViolationException从托管C (ASP.NET的包装器)调用本机C 代码
- Excel 2016 VSTO 加载项与本机代码 DLL
- 从本机代码返回"const char*"并在java中获取"字符串"