根据这个问题,我认为在C++ 17中,带有默认分配器的std :: vector应该处理对齐的类型.但是,以下代码
#include <iostream>
#include <iterator>
#include <array>
#include <vector>
template<typename T, size_t N, size_t Alignment>
struct alignas(Alignment) AlignedArray : public std::array<T, N>
{
friend std::ostream& operator<<(std::ostream& o, const AlignedArray& a)
{
std::copy(a.cbegin(), a.cend(), std::ostream_iterator<T>(o, " "));
return o;
}
};
int main()
{
using Array = AlignedArray<double, 24, 64>;
std::vector<Array> v(10);
for(const auto& e : v)
{
auto arr(e);
std::cout << arr << std::endl;
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
arr我用clang 6.0.1编译它时创建的段错误-mavx.没有-mavx开关它运行正常(CPU是E5-2697 v2).我编译了它
clang++ -I<path_to_libcxx>/include/c++/v1 -g -mavx -std=c++17 main.cpp -stdlib=libc++ -lc++abi -o alignastest -L<path_to_libcxx>/lib -L<path_to_libcxxabi>/lib.我在旧的RHEL 6.9上运行它,我编译了clang 6.0.1和libcxx,libcxxabi.我在另一个系统(Ubuntu 18.10,gcc 8)上进行了测试,它没有任何问题.
关于对齐,我发现std::aligned_alloclibc ++ 中的实现依赖于C11功能,该功能仅在最近的glibc版本(__config.h)中启用:
#if __GLIBC_PREREQ(2, 17)
#define _LIBCPP_HAS_C11_FEATURES
#endif
Run Code Online (Sandbox Code Playgroud)
不幸的是RHEL 6.9只ldd (GNU libc) 2.12安装了.难道alignas还取决于glibc的版本?
我发现了编译代码的问题,但是,我还没有找到解决方案。但看起来,这只是一个 clang 问题,使用 g++ 可以修复它。
通过显示一些生成的汇编代码可以最好地说明该问题。代码auto arr(e);行被编译为一些移动指令,以将数据从向量复制到堆栈,clang 使用(使用 -mavx 编译时)avx2 指令,如下所示(AT&T 语法):
vmovaps 0xa0(%rax),%ymm0
vmovaps %ymm0,0x120(%rsp)
...
Run Code Online (Sandbox Code Playgroud)
其中 %rax 是向量中当前数组的地址。目标 arr 位于 0x80(%rsp)。该程序将以 32 字节块(256 位 avx2 指令)的形式进行复制。
然而,当查看这些值时,问题就变得清晰了:%rax = 0x55555556be70在我的调试测试中。问题是,vmovaps(移动对齐打包单精度)到 256 位 avx2 寄存器期望目标和源在 256 位或 32 字节(0x20)边界对齐,但是 %rax 仅 16 字节对齐。当不使用alignas进行编译时,clang使用vmovups(相同的指令,但不需要对齐数据)。
所以问题是,std::vector 的分配器不尊重对齐方式,也不在 64 字节边界对齐数组。g++ 也不会将向量内的数组与 32 字节边界对齐,并且在不使用 -O[not 0] 时不使用 avx 指令。然而g++总是使用128位xmm寄存器,它只需要对齐到16字节,分配器将数据与两个编译器对齐。
编辑:
我刚刚意识到,我忘记使用 -std=c++17 进行编译。有了这个标志,它对我来说适用于 clang++。代码看起来相同,但分配器在 64 字节边界正确对齐代码。所以我猜这与一个旧图书馆有关。也许您可以将您的二进制文件发送给我,然后我可以更详细地查看它。