GCC 优化了基于固定范围的 for 循环,就好像它具有更长的可变长度一样
GCC optimizes fixed range-based for loop as if it had longer, variable length
我有一个 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 条指令(
正如 Richard Biener 在我的错误报告中所描述的那样,问题似乎是版本 8 之前的 GCC 无法理解类或结构的字段与常规变量相同的优化(例如常量循环计数(。 因此,它会发出各种花哨的代码来最佳地循环未知次数,即使在编译时已知的情况下,在容器是成员变量的情况下也是如此。
根据我的理解,这个错误可能会影响野外的相当多的代码——例如,任何成员小数组都是基于 C++11 范围的 for 循环的主题。
感谢理查德·比纳的及时解决方案(针对GCC 8(。
- 为什么两个不同的未命名名称空间可以共存于一个cpp文件中
- 如何(从固定列表中)选择一个数字序列,该序列将与目标数字相加
- c++r值引用应用于函数指针
- 如何将三维尺寸不固定的三维阵列展平为一维阵列
- 如果编译的源代码是特定于它编译的硬件的,我们如何分发它
- 如何仅使用对象名称打印特定于对象的成员
- 相当于LocaleMatcher的ICU4C
- 等<thing>效于char32_t
- 类似于strcat()的函数出现问题
- 如何将C++闭包与变量参数同时重用——类似于JavaScript
- 算术运算的结果类似于:C浮点变量中的1/3
- 如何将不同的可执行文件合并到一个窗口框架中进行编码?像浏览器一样
- 为什么在C++中对链表这样做?(像堆叠一样处理它们)
- 相当于 pybind11 中的 boost::p ython py::scope().attr()
- 堆栈和队列是否像C++中的数组一样传递?
- GCC 优化了基于固定范围的 for 循环,就好像它具有更长的可变长度一样
- 为什么std::begin()和std::end()适用于固定数组,而不适用于动态数组
- 是 膨胀/侵蚀 使用固定核进行多次迭代类似于使用更大尺寸的等效核进行膨胀/侵蚀
- 计算固定数组中的元素数量(类似于sizeof)
- 如何像直接访问文件一样使用内存:类似于read和fwrite函数