我有一系列POD结构,我试图在一个字段中求和。这是一个最小的例子:
struct Item
{
int x = 0;
int y = 0;
};
typedef Item Items[2];
struct ItemArray
{
Items items;
int sum_x1() const;
int sum_x2() const;
};
int ItemArray::sum_x1() const
{
int total = 0;
for (unsigned ii = 0; ii < 2; ++ii)
{
total += items[ii].x;
}
return total;
}
int ItemArray::sum_x2() const
{
int total = 0;
for (const Item& item : items)
{
total += item.x;
}
return total;
}
两个sum函数做同样的事情。 Clang以相同的方式编译它们。但是x86_64上带有-O3
的GCC 6却没有。这是sum_x1()
,看起来不错:
mov eax, DWORD PTR [rdi+8]
add eax, DWORD PTR [rdi]
ret
现在看看sum_x2()
:
lea rdx, [rdi+16]
lea rcx, [rdi+8]
xor eax, eax
add eax, DWORD PTR [rdi]
cmp rdx, rcx
je .L12
lea rcx, [rdi+16]
add eax, DWORD PTR [rdi+8]
cmp rdx, rcx
je .L2
lea rcx, [rdi+24]
add eax, DWORD PTR [rdi+16]
cmp rdx, rcx
je .L2
lea rcx, [rdi+32]
add eax, DWORD PTR [rdi+24]
cmp rdx, rcx
je .L2
lea rcx, [rdi+40]
add eax, DWORD PTR [rdi+32]
cmp rdx, rcx
je .L2
lea rcx, [rdi+48]
add eax, DWORD PTR [rdi+40]
cmp rdx, rcx
je .L2
lea rcx, [rdi+56]
add eax, DWORD PTR [rdi+48]
cmp rdx, rcx
je .L2
lea rcx, [rdi+64]
add eax, DWORD PTR [rdi+56]
cmp rdx, rcx
je .L2
lea rcx, [rdi+72]
add eax, DWORD PTR [rdi+64]
cmp rdx, rcx
je .L2
add eax, DWORD PTR [rdi+72]
ret
.L2:
rep ret
.L12:
rep ret
当循环长度固定为2时,为什么GCC会发出一个可变长度的展开循环,最多可达10?它只在成员函数中执行此操作 - 使sum_x2
自由函数修复它。
ICC也非常奇怪地优化sum_x2()
,尽管生成的代码完全不同。与GCC不同,sum_x2()
是成员函数还是自由函数并不重要 - 两者都不好。
我正在使用GCC 6,但是所有版本的GCC似乎都遇到了此代码的问题。添加-march=haswell
会使情况变得更糟,在大小为2的数组中添加最多15个元素的迭代.GCC 5和7生成更复杂的代码,添加SIMD指令。
我想确定此问题的确切原因,以便我可以在我的代码中找到并修复类似事件。了解在GCC 6中触发此行为的原因将非常有用。我的代码中有很多基于范围的for循环,我对删除它们的前景并不太兴奋,但如果GCC无法生成合理的代码,我将别无选择。
试一试:https://godbolt.org/g/9GK4jy
更多相关的疯狂:https://godbolt.org/g/BGYggD(最佳代码是3条指令; GCC 6产生8条指令; GCC 7产生130条指令)
答案 0 :(得分:2)
正如理查德·比纳在我的bug report中所描述的,问题似乎是版本8之前的GCC未能理解类或结构的字段受到相同的优化(例如,常量循环计数)作为常规变量。因此,它会发出各种奇特的代码,以便在容器是成员变量的情况下,即使在编译时已知,也可以最佳地循环未知次数。
我理解它的方式,这个bug可能会影响野外的相当多的代码 - 例如。成员小数组是C ++ 11基于范围的循环的主题。
感谢Richard Biener的及时解决方案(针对GCC 8)。