【发布时间】:2017-09-24 21:40:15
【问题描述】:
以下是 GCC 6 和 7 在使用 std::array 时未能优化的一些代码:
#include <array>
static constexpr size_t my_elements = 8;
class Foo
{
public:
#ifdef C_ARRAY
typedef double Vec[my_elements] alignas(32);
#else
typedef std::array<double, my_elements> Vec alignas(32);
#endif
void fun1(const Vec&);
Vec v1{{}};
};
void Foo::fun1(const Vec& __restrict__ v2)
{
for (unsigned i = 0; i < my_elements; ++i)
{
v1[i] += v2[i];
}
}
用g++ -std=c++14 -O3 -march=haswell -S -DC_ARRAY 编译上面的代码会产生很好的代码:
vmovapd ymm0, YMMWORD PTR [rdi]
vaddpd ymm0, ymm0, YMMWORD PTR [rsi]
vmovapd YMMWORD PTR [rdi], ymm0
vmovapd ymm0, YMMWORD PTR [rdi+32]
vaddpd ymm0, ymm0, YMMWORD PTR [rsi+32]
vmovapd YMMWORD PTR [rdi+32], ymm0
vzeroupper
这基本上是通过 256 位寄存器一次添加四个双精度的两个展开迭代。但是如果你在没有-DC_ARRAY 的情况下编译,你会从这个开始变得一团糟:
mov rax, rdi
shr rax, 3
neg rax
and eax, 3
je .L7
在这种情况下生成的代码(使用 std::array 而不是普通的 C 数组)似乎会检查输入数组的对齐方式——即使它在 typedef 中指定为对齐到 32 个字节。
似乎 GCC 不理解 std::array 的内容与 std::array 本身的对齐方式相同。这打破了使用 std::array 而不是 C 数组不会产生运行时成本的假设。
有没有什么简单的东西可以解决这个问题?到目前为止,我想出了一个丑陋的 hack:
void Foo::fun2(const Vec& __restrict__ v2)
{
typedef double V2 alignas(Foo::Vec);
const V2* v2a = static_cast<const V2*>(&v2[0]);
for (unsigned i = 0; i < my_elements; ++i)
{
v1[i] += v2a[i];
}
}
另请注意:如果my_elements 是4 而不是8,则不会出现问题。如果使用 Clang,则不会出现问题。
你可以在这里看到它:https://godbolt.org/g/IXIOst
【问题讨论】:
-
FWIW,clang 抱怨
alignas需要在数据成员上,而不是在 typedef 上,但如果将Vec更改为将std::array<...>作为对齐数据成员的嵌套类,并且给它operator[]重载,然后clang 确实设法优化了它。 GCC 仍然没有。 -
std::array下的数组是否与std::array具有相同的对齐方式? -
所以,显然是一个编译器错误。如果你想解决它,你应该通过 bugzilla 报告它。
-
@RustyX:虽然我希望 GCC 有朝一日能解决这个问题,但我在这里提出的问题是:我是否缺少一些简单的东西可以解决这个问题?换句话说,我想要一个相对不显眼的解决方法,它可以在 GCC 6 上实现 std::array 的最佳性能。我不想简单地为 GCC 8 屏住呼吸。
-
@RustyX:我在这里报告过:gcc.gnu.org/bugzilla/show_bug.cgi?id=80561
标签: c++ gcc optimization simd memory-alignment