在 std::vector 的模板参数中使用 alignas
Usage of alignas in template argument of std::vector
我想创建一个 std::vector
双打。但是对于 AVX2 寄存器,这些双打应该以 32 字节对齐。最好的方法是什么?我可以简单地写一些像
std::vector<alignas(32)double>
?
在此先感谢您的帮助。
如果 alignas(32)double
已编译,将要求 每个 元素分别具有 32 字节对齐,即将每个加倍填充到 32 字节,完全击败 SIMD。 (我认为它不会编译,但是与 GNU C typedef double da __attribute__((aligned(32)))
类似的东西确实可以用 sizeof(da) == 32
编译。)
请参阅 Modern approach to making std::vector allocate aligned memory 了解工作代码。
从 C++17 开始,std::vector<__m256d>
可以工作,但通常不是您想要的,因为它使标量访问变得很痛苦。
根据我的经验,C++ 对此很糟糕,尽管可能有一个标准(或 Boost)分配器采用 over-alignment 你可以用作第二个(通常是默认的)模板参数。
std::vector<double, some_aligned_allocator<32> >
仍然不是 type-compatible 和正常的 std::vector
,这是有道理的,因为任何可能重新分配它的函数都必须保持对齐。但不幸的是,即使传递给只需要 read-only 访问 std::vector
个 double
元素的函数,它也不会 type-compatible。
错位成本
在很多情况下,错位只比对齐差几个百分点,因为如果数据来自 L3 缓存或 RAM(在最近的 Intel CPUs 上),AVX/AVX2 会在数组上循环;只有使用 64 字节向量,你才会得到明显更大的惩罚(即使内存带宽仍然是瓶颈,也会有 15% 左右。)你希望 CPU 核心有时间处理它并保持相同数量的未完成 off-core 交易正在进行中。但事实并非如此。
对于 L1d 中的热数据,即使使用 32 字节向量,错位也可能造成更大的伤害。
在 x86-64 代码中,alignof(max_align_t)
在主流 C++ 实现中是 16,因此在实践中,即使 vector<double>
最终也会对齐 16,至少因为 [=20 使用的底层分配器=] 总是至少对齐那么多。但这通常是 16 的奇数倍,至少在 GNU/Linux 上是这样。 Glibc 的分配器(也被 malloc 使用)用于大型分配使用 mmap
来获取整个页面范围,但它保留前 16 个字节用于簿记信息。这对 AVX 和 AVX-512 来说是不幸的,因为这意味着除非您使用对齐分配,否则您的数组总是未对齐。 ()
主流 std::vector
实现在必须增长时也很低效:C++ 不提供与 new/delete 兼容的 realloc
等价物,因此它总是必须分配更多 space 并复制到开头。永远不要尝试分配更多 space 与现有映射连续(即使对于 non-trivially-copyable 类型也是安全的),并且不使用像 Linux mremap
这样的 implementation-specific 技巧来映射相同的映射物理页面到不同的虚拟地址,而不必复制所有这些 mega/gigabytes。 C++ 允许代码重新定义 operator new
这一事实意味着 std::vector 的库实现也不能只使用更好的分配器。所有这些都是 non-problem 如果您 .reserve
您将需要的尺寸,但它非常愚蠢。
我想创建一个 std::vector
双打。但是对于 AVX2 寄存器,这些双打应该以 32 字节对齐。最好的方法是什么?我可以简单地写一些像
std::vector<alignas(32)double>
?
在此先感谢您的帮助。
如果 alignas(32)double
已编译,将要求 每个 元素分别具有 32 字节对齐,即将每个加倍填充到 32 字节,完全击败 SIMD。 (我认为它不会编译,但是与 GNU C typedef double da __attribute__((aligned(32)))
类似的东西确实可以用 sizeof(da) == 32
编译。)
请参阅 Modern approach to making std::vector allocate aligned memory 了解工作代码。
从 C++17 开始,std::vector<__m256d>
可以工作,但通常不是您想要的,因为它使标量访问变得很痛苦。
根据我的经验,C++ 对此很糟糕,尽管可能有一个标准(或 Boost)分配器采用 over-alignment 你可以用作第二个(通常是默认的)模板参数。
std::vector<double, some_aligned_allocator<32> >
仍然不是 type-compatible 和正常的 std::vector
,这是有道理的,因为任何可能重新分配它的函数都必须保持对齐。但不幸的是,即使传递给只需要 read-only 访问 std::vector
个 double
元素的函数,它也不会 type-compatible。
错位成本
在很多情况下,错位只比对齐差几个百分点,因为如果数据来自 L3 缓存或 RAM(在最近的 Intel CPUs 上),AVX/AVX2 会在数组上循环;只有使用 64 字节向量,你才会得到明显更大的惩罚(即使内存带宽仍然是瓶颈,也会有 15% 左右。)你希望 CPU 核心有时间处理它并保持相同数量的未完成 off-core 交易正在进行中。但事实并非如此。
对于 L1d 中的热数据,即使使用 32 字节向量,错位也可能造成更大的伤害。
在 x86-64 代码中,alignof(max_align_t)
在主流 C++ 实现中是 16,因此在实践中,即使 vector<double>
最终也会对齐 16,至少因为 [=20 使用的底层分配器=] 总是至少对齐那么多。但这通常是 16 的奇数倍,至少在 GNU/Linux 上是这样。 Glibc 的分配器(也被 malloc 使用)用于大型分配使用 mmap
来获取整个页面范围,但它保留前 16 个字节用于簿记信息。这对 AVX 和 AVX-512 来说是不幸的,因为这意味着除非您使用对齐分配,否则您的数组总是未对齐。 (
主流 std::vector
实现在必须增长时也很低效:C++ 不提供与 new/delete 兼容的 realloc
等价物,因此它总是必须分配更多 space 并复制到开头。永远不要尝试分配更多 space 与现有映射连续(即使对于 non-trivially-copyable 类型也是安全的),并且不使用像 Linux mremap
这样的 implementation-specific 技巧来映射相同的映射物理页面到不同的虚拟地址,而不必复制所有这些 mega/gigabytes。 C++ 允许代码重新定义 operator new
这一事实意味着 std::vector 的库实现也不能只使用更好的分配器。所有这些都是 non-problem 如果您 .reserve
您将需要的尺寸,但它非常愚蠢。