我正在使用 Agner Fog 的 vector 类库在我的应用程序中使用 SIMD 指令(特别是 AVX)。由于最好使用数组结构数据结构来轻松使用 SIMD,因此我经常使用:
std::vector<Vec8d> some_var;
甚至
struct some_struct {
std::vector<Vec8d> a;
std::vector<Vec8d> b;
}
考虑到 std::vector 内部 Vec8d* 数组实际上可能未对齐,我想知道这是否不好(性能方面甚至完全错误?)?
最佳答案
我通常会使用 vector<double>
和标准 SIMD 加载/存储内在函数来访问数据。这避免将接口(interface)和接触它的所有代码绑定(bind)到特定的 SIMD vector 宽度和包装器库。您仍然可以将大小填充为 8 double 的倍数,这样您就不必在循环中包含清理处理。
但是,您可能希望为该 vector<double>
使用自定义分配器所以你可以让它对齐你的 double 。不幸的是,即使该分配器的底层内存分配与 new/delete 兼容,它也将具有与 vector<double>
不同的 C++ 类型。所以如果你在其他地方使用它,你不能自由地将它分配/移动到这样的容器。
我担心如果您确实想要访问个人 double
你的 vector 元素,做Vec8vec[i][j]
可能会导致比 operator[]
更糟糕的 asm(例如,加载 SIMD,然后从 VCL 的 vecdouble[i*8 + j]
进行洗牌或存储/重新加载) (可能只是一个 vmovsd
),特别是如果这意味着您需要编写一个嵌套循环,否则您将不需要它。
avec.load (&doublevec[8]);
应该生成(几乎或完全)与 avec = Vec8vec[1];
相同的 asm .如果数据在内存中,编译器将需要使用加载指令来加载它。它有什么“类型”并不重要;类型是 C++ 的东西,而不是 asm 的东西; SIMD vector 只是对内存中某些字节的重新解释。
但如果这是说服 C++17 编译器按 64 位对齐动态数组的最简单方法,那么它可能值得考虑。如果/当移植到 ARM NEON 或 SVE 时仍然令人讨厌并且会导致 future 的痛苦,因为我上次检查时 Agner 的 VCL 仅包装 x86 SIMD。甚至移植到 AVX2 也很糟糕。
更好的方法可能是自定义分配器(我认为 Boost 已经编写了一些),您可以将其用作 std::vector<double, aligned_allocator<64>>
之类的第二个模板参数。 .这也与 std::vector<double>
类型不兼容如果你想传递它并将它分配给其他vector<>
s,但至少它没有专门绑定(bind)到 AVX512。
如果您不使用 C++17 编译器(因此 std::vector 不遵守 alignof(T) > alignof(max_align_t) 即 16),那么甚至不要考虑一下;当像 GCC 和 Clang 这样的编译器使用 vmovapd
时它会出错(需要对齐)存储 __m512d
.
您需要使数据保持一致;与当前 AVX512 CPU (Skylake-X) 上的 AVX2 相比,64 字节对齐对 AVX512 的影响更大。
MSVC(我认为是 ICC)出于某种原因选择始终使用未对齐的加载/存储指令(除了将加载折叠到内存源操作数中,即使使用遗留的 SSE 指令,因此需要 16 字节对齐),即使在编译时对齐时也是如此保证存在。我想这就是它恰好适合您的原因。
对于 SoA 数据布局,您可能希望所有数组共享一个通用大小,并使用 aligned_alloc
(与 free
兼容,而不是 delete
)或类似管理 double *
尺寸的东西成员。不幸的是,没有支持 aligned_realloc 的标准对齐分配器,所以你总是必须复制,即使你的数组后面有空闲的虚拟地址空间,一个不糟糕的 API 可以让你的数组在不复制的情况下增长。谢谢,C++。
关于c++ - 使用 std::vector<Vec8d> 是好是坏(性能方面),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66062171/