迭代 const 容器时编译器会展开“for”循环吗?

Posted

技术标签:

【中文标题】迭代 const 容器时编译器会展开“for”循环吗?【英文标题】:Will compiler unroll "for" loop when iterating a const container? 【发布时间】:2020-08-31 14:28:10 【问题描述】:

在 C++11 中,我们可以在迭代容器时使用更简单的“for”循环,如下所示:

for (auto i : 1, 2, 3, 4)
    ...;

但是,我不知道此类代码的效率。具体来说:

1、2、3、4 的类型是什么?是原始数组,还是转换成std::vector等其他容器? 编译器会展开循环吗?

更新:假设我们使用-O2,循环中的代码只是几个操作。 就我而言,我想列举四个方向 UP DOWN LEFT RIGHT 并使用方向参数调用一个函数。我只关心程序是否能有最好的性能。

非常感谢!

【问题讨论】:

编译器会展开循环吗? 这取决于你在循环中做了什么。您可以使用godbolt.org 来探索各种编译器会做什么。 编译器会展开循环吗?取决于编译器、优化级别以及可能的其他因素。我不知道循环展开有任何保证。 "1, 2, 3, 4 的类型是什么?"它是std::initializer_listen.cppreference.com/w/cpp/utility/initializer_list 你真的关心编译器是否展开循环吗?还是您只关心生成的可执行文件的性能?那些不一样。通常编译器在决定何时应用优化和何时不应用优化方面要好得多 @zbh2047:测量、优化、测量。在你确定存在问题之前不要担心这样的微优化在特定的代码中。大多数时候性能取决于高级算法的效率,而不是某个循环是否被编译器展开。 【参考方案1】:

1、2、3、4的类型是什么?

std::initializer_list 将从该初始化程序构造。正在迭代。您甚至需要包含 <initializer_list> 才能使其正常工作。

编译器会展开循环吗?

该语言不保证循环展开。您可以通过编译和检查生成的程序集来了解特定编译器是否使用特定目标 CPU 的特定选项展开特定循环。

也就是说,迭代次数在编译时是已知的,因此编译器有可能展开整个循环。


假设我们使用 -O2

不管怎样,-O2 不启用 -funroll-loops。在添加该选项之前,请阅读其文档:

-funroll-loops

展开循环,其迭代次数可以在编译时或进入循环时确定。 -funroll-loops 意味着 -frerun-cse-after-loop。此选项使代码更大,可能会也可能不会使其运行得更快。

在这个例子中,Clang 确实展开了循环:https://godbolt.org/z/enKzMh,而 GCC 没有:https://godbolt.org/z/ocfor8

【讨论】:

【参考方案2】:

无法保证,但编译器可以优化某些情况,因此您很有可能最终得到好的代码。

例如,可以完全优化掉那个:

#include <initializer_list>

// Type your code here, or load an example.
int sum() 
    int sum = 0;
    for (auto i : 1, 2, 3, 4) 
        sum += i;
    
    return sum;


int main() 
  return sum();

https://godbolt.org/z/racnKf

-O3编译,gcc可以推导出计算的结果是10:

sum():
        mov     eax, 10
        ret
main:
        mov     eax, 10
        ret

在现实世界的示例中,编译器可能无法对其进行优化,因此您必须自己验证。

【讨论】:

以上是关于迭代 const 容器时编译器会展开“for”循环吗?的主要内容,如果未能解决你的问题,请参考以下文章

循环展开与循环平铺

OpenCL Unrolling Loops优化

ECMAScript 2015:for 循环中的 const

为啥这个 const auto 变量在 range-for 循环中为类的 const 成员函数编译?

智能指针的迭代和容器

C/C++ 中的自展开宏循环