【问题标题】:Would the compiler optimize this for loop?编译器会优化这个 for 循环吗?
【发布时间】:2017-01-27 02:58:27
【问题描述】:

在 C 或 C++ 中,如果编译器遇到 for 循环,其中计数器从 0 计数到 n,并且 n 是一个变量(不是函数调用,并且 NOT一个常量),编译器是否会通过检查变量n(绑定变量)是否会在循环期间更改来优化循环(访问以进行写入,例如:n 的长度可以是在循环之前计算的字符串),这里通过优化是指将其值复制到寄存器以避免内存访问?

这是一个例子:

for (int c = 0; c < n; c++) {
    // do something not related to n
}

编译器会注意到并优化它吗?

【问题讨论】:

  • 试试看。 (填..)
  • 我猜也取决于编译器。去这里看看会发生什么:gcc.godbolt.org
  • 尝试使用-O0-O1-O2 编译您的代码,看看GCC 在这些选项中的表现如何
  • 另外,取决于你在 for 循环中所做的事情,如果它没有明显的副作用,它可以优化整个事情。
  • 必须真正确定这样的变量没有改变;如果n 既不是局部变量也不是内部链接变量(例如,它是全局或对象字段)并且循环体调用函数(特别是在其他 TU 中定义的函数),编译器极难执行这种优化(也许使用 LTCG)。这就是为什么通常在这些情况下您要确保循环限制变量是本地变量。

标签: c++ c gcc optimization


【解决方案1】:

是的,至少 GCC 和大多数其他编译器会这样做。如果您尝试查看 for 循环变量是什么,当您使用 -O1 或更高版本进行编译时,调试器会说该变量已被优化。当然,编译器不需要这样做。这就是标准所说的。

for 语句

for (for-init-statement; conditionopt; expressionopt) statement

等价于

{
    for-init-statement
    while (condition) {
        statement
        expression
    }
}

【讨论】:

  • 这是一个非常危险的假设。这取决于太多因素,包括循环中的代码或目标架构。一般来说,这是错误的。
  • 这完全不正确。编译器允许n 存储在寄存器中,前提是它可以证明它在其他地方没有别名(即没有指向同一内存或其他东西的指针),但这很难为了在一般情况下证明,尤其是如果n 是全局或文件范围,或者如果您曾经在某处传递了指向n 的指针。此外,系统上只有这么多寄存器,编译器可能不得不在循环体中使用它们。特别是 x86-32 系统具有少量寄存器,并且有许多破坏它们的指令。你不能在这里假设“是”
  • 在我检查的情况下,编译器确实优化了一个寄存器,其余的答案解释说不需要优化(所有优化都是如此)。
  • 而且我可以生成一个它不会将其放入寄存器的案例。由于该问题仅将循环体描述为//do something not related to n,因此答案是“也许有时”,而不是“是”。稍后您说编译器不必这样做的部分很好,但最初的答案确实具有误导性。 (并且 for/while 等价性大多是准确的,但并不真正相关。)
【解决方案2】:

结果取决于使用的编译器。

编译器可以为 n 使用处理器寄存器,您仍然可以在循环内修改 n。所以无论如何,最小的优化都是可能的。

如果您有一个指向 n 的指针并且您使用指针间接更改 n 的值,则将变量的值放在处理器寄存器中可能会导致“别名”问题

例如:

int n = 4;
int *nptr = &n;
for(int i = 0; i < n; ++i)
  --*nptr;

编译器必须知道 nptr 是 n 的别名,因此每次访问时都必须从内存中读取 n,但在许多情况下编译器根本没有机会知道 n 和 nptr 之间的关系。

您可以使用 volatile 关键字来阻止编译器优化变量(即volatile int n = 4;

【讨论】:

    【解决方案3】:

    您应该尝试编译并自己查看。编译器的优化取决于几件事。

    无论如何,为了回答你的问题,我唯一能做的就是提供一个与你最相似的具体案例。

    代码很简单:

    #include <string>
    
    int main(int argc, char *argv[]) {
      std::string str = "this_is_a_string";
      int size = str.size();
      for (int i = 0; i < size; ++i) {
        str += std::to_string(i);
      }
      return 0;
    }
    

    结果汇编代码为(针对不同优化级别):

    GCC 6.2 -O0

     movl   $0x0,-0x14(%rbp)    // int i = 0;
     mov    -0x14(%rbp),%eax    // load i into the register
     cmp    -0x18(%rbp),%eax    // load size and compare with i in the register
     jge    401317 <main+0x91>  // jump if >=
    

    GCC 6.2 -O1

     // initialization up
     add    $0x1,%ebx         // ++i (now i is stored in register)
     cmp    %ebx,-0x5c(%ebp)  // compare i and size (which is load from memory)
     je     0x80488a3 <main(int, char**)+136>  // jump if = (and not >=)
    

    GCC 6.2 -O2

    同样的-O1

    Here 使用的代码,带有程序集。

    【讨论】:

      【解决方案4】:

      c/c++ 编译器是否会通过检查变量 n(绑定变量)在循环期间是否会更改来优化循环

      “as-if”规则允许编译器重新排序或重写代码,只要可观察到的效果是“好像”它执行了您编写的代码。

      这里的关键字是“可观察的”。除非可以证明n 在循环体执行期间不能改变,否则循环必须为每次迭代重新评估c &lt; n

      如果编译器可以访问循环中的所有代码(例如,它是在同一个编译单元中定义的,或者在链接期间全局优化有不同的外观,n 永远不会被引用或指针别名)然后它很可能能够证明n 不会改变。

      在这种情况下,您应该不会对看到循环以某种方式进行优化感到惊讶。

      【讨论】:

        猜你喜欢
        • 2011-10-30
        • 1970-01-01
        • 2011-03-04
        • 2011-09-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-12-11
        • 1970-01-01
        相关资源
        最近更新 更多