167

我只是想知道如果您执行以下操作是否会降低速度或效率:

int i = 0;
while(i < 100)
{
    int var = 4;
    i++;
}

宣布int var一百次。在我看来会有,但我不确定。这样做会更实用/更快吗:

int i = 0;
int var;
while(i < 100)
{
    var = 4;
    i++;
}

还是它们在速度和效率方面是相同的?

4

13 回答 13

199

局部变量的堆栈空间通常在函数范围内分配。因此,循环内部不会发生堆栈指针调整,只需将 4 分配给var. 因此,这两个片段具有相同的开销。

于 2009-06-11T19:03:52.007 回答
113

对于原始类型和 POD 类型,没有区别。在这两种情况下,编译器都会在函数的开头为变量分配堆栈空间,并在函数返回时释放它。

对于具有非平凡构造函数的非 POD 类类型,它会有所不同——在这种情况下,将变量放在循环之外只会调用构造函数和析构函数一次,并且每次迭代都会调用赋值运算符,而将其放在循环中循环将为循环的每次迭代调用构造函数和析构函数。根据类的构造函数、析构函数和赋值运算符的作用,这可能是可取的,也可能是不可取的。

于 2009-06-11T19:05:10.710 回答
70

它们都是相同的,通过查看编译器的功能(即使没有将优化设置为高),您可以通过以下方式找到答案:

看看编译器(gcc 4.0)对你的简单例子做了什么:

1.c:

main(){ int var; while(int i < 100) { var = 4; } }

gcc -S 1.c

1.秒:

_main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    $0, -16(%ebp)
    jmp L2
L3:
    movl    $4, -12(%ebp)
L2:
    cmpl    $99, -16(%ebp)
    jle L3
    leave
    ret

2.c

main() { while(int i < 100) { int var = 4; } }

gcc -S 2.c

2.s:

_main:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        movl    $0, -16(%ebp)
        jmp     L2
L3:
        movl    $4, -12(%ebp)
L2:
        cmpl    $99, -16(%ebp)
        jle     L3
        leave
        ret

从这些中,您可以看到两件事:首先,两者的代码相同。

其次, var 的存储空间是在循环外分配的:

         subl    $24, %esp

最后,循环中唯一的事情是赋值和条件检查:

L3:
        movl    $4, -12(%ebp)
L2:
        cmpl    $99, -16(%ebp)
        jle     L3

在不完全删除循环的情况下,这与您的效率一样高。

于 2009-06-11T19:55:47.093 回答
14

现在最好在循环中声明它,除非它是一个常量,因为编译器将能够更好地优化代码(减少变量范围)。

编辑:这个答案现在大部分已经过时了。随着后经典编译器的兴起,编译器无法弄清楚的情况越来越少。我仍然可以构造它们,但大多数人会将构造归类为糟糕的代码。

于 2009-06-11T19:02:28.110 回答
11

大多数现代编译器都会为您优化这一点。话虽如此,我会使用您的第一个示例,因为我发现它更具可读性。

于 2009-06-11T19:01:58.063 回答
9

对于内置类型,两种样式之间可能没有区别(可能一直到生成的代码)。

但是,如果变量是具有重要构造函数/析构函数的类,则运行时成本很可能存在重大差异。我通常会将变量的范围限定在循环内部(以保持范围尽可能小),但如果这会产生性能影响,我会考虑将类变量移到循环范围之外。但是,这样做需要一些额外的分析,因为 ode 路径的语义可能会发生变化,因此只有在语义允许的情况下才能这样做。

RAII 类可能需要这种行为。例如,管理文件访问生命周期的类可能需要在每次循环迭代时创建和销毁,以正确管理文件访问。

假设您有一个LockMgr类,它在构造时获取临界区并在销毁时释放它:

while (i< 100) {
    LockMgr lock( myCriticalSection); // acquires a critical section at start of
                                      //    each loop iteration

    // do stuff...

}   // critical section is released at end of each loop iteration

完全不同于:

LockMgr lock( myCriticalSection);
while (i< 100) {

    // do stuff...

}
于 2009-06-12T00:41:58.247 回答
7

两个循环具有相同的效率。它们都将花费无限的时间:) 在循环内增加 i 可能是个好主意。

于 2009-06-11T20:27:15.177 回答
2

我曾经进行了一些性能测试,令我惊讶的是,发现案例 1 实际上更快!我想这可能是因为在循环内声明变量会减少它的范围,所以它会更早地被释放。然而,那是很久以前的事了,在一个非常古老的编译器上。我确信现代编译器在优化差异方面做得更好,但保持变量范围尽可能短仍然没有坏处。

于 2015-01-27T14:13:10.717 回答
2
#include <stdio.h>
int main()
{
    for(int i = 0; i < 10; i++)
    {
        int test;
        if(i == 0)
            test = 100;
        printf("%d\n", test);
    }
}

上面的代码总是打印 100 10 次,这意味着循环内的局部变量在每个函数调用中只分配一次。

于 2016-08-13T14:57:06.480 回答
0

唯一可以确定的方法是给它们计时。但是,如果存在差异,那将是微观的,因此您将需要一个强大的大时序循环。

更重要的是,第一个样式更好,因为它初始化变量 var,而另一个未初始化。这一点以及应该在尽可能接近其使用点的地方定义变量的指导方针,意味着通常应该首选第一种形式。

于 2009-06-11T19:03:45.237 回答
-1

只有两个变量,编译器可能会为这两个变量分配一个寄存器。这些寄存器无论如何都在那里,所以这不需要时间。在任何一种情况下,都有 2 个寄存器写入和一个寄存器读取指令。

于 2009-06-12T15:20:57.917 回答
-2

我认为大多数答案都缺少一个需要考虑的重点:“清楚吗”,显然所有的讨论都是事实;不它不是。我建议在大多数循环代码中效率几乎不是问题(除非你计算火星着陆器),所以真正唯一的问题是看起来更明智、可读和可维护的 - 在这种情况下,我建议声明循环前面和外部的变量 - 这只是让它更清晰。那么像你我这样的人甚至都不会浪费时间在网上检查它是否有效。

于 2014-03-05T05:20:55.407 回答
-6

那不是真的有开销但是它可以忽略的开销。

即使它们可能最终会在堆栈上的同一位置它仍然分配它。它将在堆栈上为该 int 分配内存位置,然后在 } 结束时释放它。不是在堆自由意义上它会将sp(堆栈指针)移动1。在你的情况下,考虑到它只有一个局部变量,它只会简单地等同于fp(帧指针)和sp

简短的回答是:不要在意任何一种方式几乎都一样。

但是尝试阅读更多关于堆栈是如何组织的。我的本科学校对此进行了很好的讲座如果您想阅读更多内容,请点击此处 http://www.cs.utk.edu/~plank/plank/classes/cs360/360/notes/Assembler1/lecture.html

于 2009-06-11T19:24:44.547 回答