- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在考虑编写一个 SIMD vector 数学库,因此作为一个快速基准,我编写了一个程序,该程序执行 1 亿(4 个 float ) vector 元素乘法并将它们加到累积总数中。对于我的经典非 SIMD 变体,我只是制作了一个具有 4 个 float 的结构,并编写了我自己的乘法函数“multiplyTwo”,该函数将两个这样的结构元素明智地相乘并返回另一个结构。对于我的 SIMD 变体,我使用了“immintrin.h”以及 __m128、_mm_set_ps 和 _mm_mul_ps。我在 i7-8565U 处理器(威士忌湖)上运行并编译:g++ main.cpp -mavx -o test.exe
以在 GCC 中启用 AVX 扩展指令。
奇怪的是,SIMD 版本大约需要 1.4 秒,而非 SIMD 版本只需要 1 秒。我觉得好像我做错了什么,因为我认为 SIMD 版本的运行速度应该快 4 倍。感谢任何帮助,代码如下。我已将非 SIMD 代码放在注释中,当前形式的代码是 SIMD 版本。
#include "immintrin.h" // for AVX
#include <iostream>
struct NonSIMDVec {
float x, y, z, w;
};
NonSIMDVec multiplyTwo(const NonSIMDVec& a, const NonSIMDVec& b);
int main() {
union { __m128 result; float res[4]; };
// union { NonSIMDVec result; float res[4]; };
float total = 0;
for(unsigned i = 0; i < 100000000; ++i) {
__m128 a4 = _mm_set_ps(0.0000002f, 1.23f, 2.0f, (float)i);
__m128 b4 = _mm_set_ps((float)i, 1.3f, 2.0f, 0.000001f);
// NonSIMDVec a4 = {0.0000002f, 1.23f, 2.0f, (float)i};
// NonSIMDVec b4 = {(float)i, 1.3f, 2.0f, 0.000001f};
result = _mm_mul_ps(a4, b4);
// result = multiplyTwo(a4, b4);
total += res[0];
total += res[1];
total += res[2];
total += res[3];
}
std::cout << total << '\n';
}
NonSIMDVec multiplyTwo(const NonSIMDVec& a, const NonSIMDVec& b)
{ return {a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w}; }
最佳答案
禁用优化(gcc 默认为 -O0
),内部函数通常很糟糕。 Anti-optimized -O0
code-gen对于内在函数通常会造成很大伤害(甚至比标量还要大),并且一些类似函数的内在函数会引入额外的存储/重新加载开销。此外,-O0
的额外存储转发延迟往往会造成更大的伤害,因为当您使用 1 个 vector 而不是 4 个标量进行操作时,ILP 更少。
使用 gcc -march=native -O3
但即使启用了优化,您的代码仍然会通过对循环内的每个 vector 进行水平相加来破坏 SIMD 的性能。参见 How to Calculate Vector Dot Product Using SSE Intrinsic Functions in C如何不这样做:使用_mm_add_ps
来累积一个__m128 total
vector ,并且只在循环外对其进行水平求和。
通过在循环内执行标量 total +=
,您可以在 FP-add 延迟上瓶颈循环。该循环携带的依赖链意味着您的循环在您的 Skylake 派生微体系结构上每 4 个周期的运行速度不能超过 1 个 float
,其中 addss
延迟为 4 个周期。 ( https://agner.org/optimize/ )
甚至比 __m128 total
更好,使用 4 或 8 个 vector 来隐藏 FP 添加延迟,因此您的 SIMD 循环可以在 mul/add(或 FMA)吞吐量而不是延迟上成为瓶颈。
一旦你解决了这个问题,正如@harold 指出的那样,你在循环内使用 _mm_set_ps
的方式将导致编译器产生非常糟糕的 asm。当操作数不是常量或至少是循环不变的时,在循环内部不是一个好的选择。
你这里的例子显然是人为的;通常你会从内存中加载 SIMD vector 。但是,如果您确实需要更新 __m128
vector 中的循环计数器,您可以使用 tmp = _mm_add_ps(tmp, _mm_set_ps(1.0, 0, 0, 0))
。或通过添加 1.0、2.0、3.0 和 4.0 展开,因此循环携带的依赖项只是一个元素中的 += 4.0。
x + 0.0
是恒等操作,即使对于 FP(可能带符号零除外),因此您可以对其他元素执行此操作而无需更改它们。
或者对于 vector 的低位元素,您可以使用 _mm_add_ss
(标量)仅修改它。
关于c++ - 为什么这个简单的 C++ SIMD 基准测试在使用 SIMD 指令时运行得更慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58365789/
我是一名优秀的程序员,十分优秀!