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;
}

这两个求和函数做同样的事情。 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 在我的 bug report 中所描述的那样,问题似乎是版本 8 之前的 GCC 无法理解 class 或结构的字段受到相同的优化(例如,常量循环计数)作为常规变量。因此它会发出各种奇特的代码以优化循环未知次数,即使在编译时已知,在容器是成员变量的情况下。

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

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