GCC优化基于固定范围的for循环,就好像它具有更长,可变长度一样

Joh*_*nck 26 c++ optimization gcc icc c++11

我有一系列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;
}
Run Code Online (Sandbox Code Playgroud)

两个sum函数做同样的事情.Clang以相同的方式编译它们.但是-O3在x86_64上的GCC 6 却没有.这是sum_x1(),看起来不错:

  mov eax, DWORD PTR [rdi+8]
  add eax, DWORD PTR [rdi]
  ret
Run Code Online (Sandbox Code Playgroud)

现在看看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
Run Code Online (Sandbox Code Playgroud)

当循环长度固定为2时,为什么GCC会发出一个可变长度的展开循环,最多可达10?它只在成员函数中执行此操作 - 使sum_x2自由函数修复它.

sum_x2()虽然生成的代码完全不同,但ICC也非常奇怪地进行了优化.与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条指令)

Joh*_*nck 3

正如 Richard Biener 在我的错误报告中所述,问题似乎是版本 8 之前的 GCC 无法理解类或结构的字段与常规变量受到相同的优化(例如恒定循环计数)。因此,在容器是成员变量的情况下,即使在编译时已知,它也会发出各种奇特的代码以最佳地循环未知次数。

据我了解,这个 bug 可能会影响相当多的代码——例如,成员小数组是 C++11 基于范围的 for 循环的主题的任何地方。

感谢 Richard Biener 的及时解决(针对 GCC 8)。