【问题标题】:Is it efficient to declare a variable in a loop? [duplicate]在循环中声明变量是否有效? [复制]
【发布时间】:2013-05-26 17:04:59
【问题描述】:

如果我执行以下操作之一(我假设对于这个问题来说是等效的)

for(int i=0; i<A; i++)
{
  //... do stuff
  for(int j=0; j<B; j++)
  {
    //... do stuff
  }
  //... do stuff
}

for(int i=0; i<A; i++)
{
  int j;
  //... do stuff
}

变量j 是否在每个循环中都在堆栈上重新创建(SP 是否在每个循环中不断更新),或者编译器是否足够聪明,可以知道一个函数一次可能有多少个局部变量,以及然后在函数入口处为堆栈上的所有这些腾出空间?

我知道这在理论上是依赖于编译器的,但我假设像这样的简单事情在所有主要编译器中都很常见。如果没有,是否有人特别了解 GCC 和 VC++ 编译器?

【问题讨论】:

  • 您可以查看使用不同优化级别生成的程序集。变量很可能只会创建一次。
  • 通常会被优化掉。不要费心在外面宣布它。没必要。

标签: c++ c compiler-optimization


【解决方案1】:

效率很高。 gcc 将优化它,就像大多数现代编译器一样。还要记住 Donald Knuth 说过的话:

我们应该忘记小的效率,比如大约 97% 的时间: 过早优化是万恶之源。

您可以通过比较汇编代码来检查它是否有效。例如,使用diff 进行比较。要生成程序集,请使用 -S 标志 gcc -S。相当于gcc -S -O0,因为默认优化级别为0。所以,即使在最低级别的优化中,gcc 也会为您处理这个变量。

第一个版本(为了更好的可读性更喜欢这种方式):

#include <stdio.h>

int main()
{
    for (int i = 0; i < 10; i++)
    {
        for (int j = 0; j < 10; j++)
        {
            printf("%d ", i + j);
        }
    }
    return 0;
}

第二版:

#include <stdio.h>

int main()
{
    for (int i = 0; i < 10; i++)
    {
        int j;
        for (j = 0; j < 10; j++)
        {
            printf("%d ", i + j);
        }
    }
    return 0;
}

相同的组装结果:

    .file   "main.cpp"
    .section    .rodata
.LC0:
    .string "%d "
    .text
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    pushq   %rbp
    .cfi_def_cfa_offset 16
    movq    %rsp, %rbp
    .cfi_offset 6, -16
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    $0, -4(%rbp)
    jmp .L2
.L5:
    movl    $0, -8(%rbp)
    jmp .L3
.L4:
    movl    -8(%rbp), %eax
    movl    -4(%rbp), %edx
    leal    (%rdx,%rax), %eax
    movl    %eax, %esi
    movl    $.LC0, %edi
    movl    $0, %eax
    call    printf
    addl    $1, -8(%rbp)
.L3:
    cmpl    $9, -8(%rbp)
    setle   %al
    testb   %al, %al
    jne .L4
    addl    $1, -4(%rbp)
.L2:
    cmpl    $9, -4(%rbp)
    setle   %al
    testb   %al, %al
    jne .L5
    movl    $0, %eax
    leave
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3"
    .section    .note.GNU-stack,"",@progbits

int i 是 -4(%rbp),int j 是 -8(%rbp)。如您所见,int j 没有重新分配或其他什么。

【讨论】:

  • 如果您应用 other 优化规则,“不要基于不具代表性的测试用例进行概括”,您的 Knuth 报价看起来会更加严肃。您的嵌套 for 循环没有任何效果,任何值得其盐的编译器都会优化 f() 到 return 0; 的两个版本,但这可能或可能不意味着编译器总是在循环的更常见情况下生成相同的代码不是微不足道的。
  • 它并没有优化为 return 0,但我明白你的意思。我编辑了,谢谢。组装还是一样的。
【解决方案2】:

我相信变量只会被创建一次,虽然我不在乎,我也不相信你应该这样做。

这可能是您进行预优化(或不必要的优化)的一个示例;在循环中声明变量所造成的潜在低效率非常小,通过在不同位置声明变量来“优化”代码对程序的整体运行时和内存使用的影响可以忽略不计。

考虑花时间优化您的算法并寻找有效的数据结构,因为这可能会更好地利用您的时间。

【讨论】:

  • 在某种程度上,这是一个关于编译器的问题,而不是关于代码优化的问题。我实际上并不关心这个,只是好奇。
  • 很抱歉,但“我相信”和“我不在乎”会取消回答的资格。这个问题是有效的,值得比这更详细一点。也许,虽然不太可能,但这个循环是某些超级计算机大部分时间都在使用的循环。
【解决方案3】:

今天的编译器功能太强大了,无法优化这些东西。因此,请不要打扰并根据您的方便使用。过早的优化更邪恶。

【讨论】:

    【解决方案4】:

    为什么不自己试试看呢?

    class foo {
    public:
        foo () { std::cout << "Construct\n"; }
        ~foo () { std::cout << "Destruct\n"; }
        };
    
    int main () {
        for ( int i = 0; i < 10; ++i ) {
            foo f;
        }
    
        return 0;
    }
    

    您会看到f 的构造函数(和析构函数!)在每次循环中都会被调用。因此,您的问题的答案是“是的,每次通过循环都会重新创建变量。”

    回到你的例子,你声明了一个 int,它有一个构造函数和析构函数,它们什么都不做。因此,在循环中声明 int 不会降低性能。

    【讨论】:

    • 构造函数和析构函数是独立于堆栈调整的问题。
    • 这与内置类型不同,因为内置没有隐式构造函数/析构函数或初始化。
    • 我在询问堆栈调整。可以通过放置new 调用构造函数而无需调整堆栈,这无论如何都会发生在对象的每个构造中(分配内存,然后为该内存调用构造函数)
    • 好的。对困惑感到抱歉。不过,堆栈调整真的很便宜(更改寄存器中的值)
    猜你喜欢
    • 2021-05-18
    • 1970-01-01
    • 2022-11-04
    • 1970-01-01
    • 1970-01-01
    • 2012-09-19
    • 2014-01-10
    • 1970-01-01
    • 2011-12-09
    相关资源
    最近更新 更多