VS2010 C ++ / C#编译器能否优化掉循环内部声明的变量?

时间:2010-12-02 02:31:33

标签: c# c++ coding-style performance

我在我的位置上很新,所以在表达担忧之前我应该​​三思而后行,但是我看到的一些代码......

当我试图提高可读性时,我被告知并不总是有时间,效率更重要。

但后来我看到不同类型的循环中的变量重新声明,有时下降到两个级别。我的一部分认为 - 不要那样做!但另一部分说 - 无论如何,这个复杂的功能应该分解成几个功能。那些较小的函数可以有临时变量,编译器应该能够处理它们。

然后函数调用会增加一些额外的成本。让我试着提出两个例子:

Class1::Do1()
{
    for (int i = 0; i < 100; i++)
    {
        bool x = GetSomeValue();
        ...
        if (x)
        {
            ...
        }
    } 
}

VS

Class1::Do1()
{
    bool x = false;
    for (int i = 0; i < 100; i++)
    {
        x = GetSomeValue();
        ...
        if (x)
        {
            ...
        }
    } 
}

vs

Class1::Do1()
{
    for (int i = 0; i < 100; i++)
    {
        Do2();
    } 
}

Class1::Do2()
{
    bool x = GetSomeValue();
    ...
    if (x)
    {
        ...
    }
}

第一种方式对我来说是错误的,当我自己编写代码时,我总是喜欢第二种甚至第三种。我认为由于额外的函数调用,第三种方式可能会更慢。第一种方式有时甚至可能看起来粗略 - 如果函数很长,声明将远离使用它的位置。另一件事是我的例子太简单了 - 编译器可能弄清楚如何简化,也许可以内联所有3.不幸的是现在我不记得其他我认为是邋iness的例子,只是想提一下变量是重新宣布n * m次,因为它们是两级深度(在2个循环内)。

魔鬼的拥护者说 - 你怎么知道100%这可能效率不高?我的纯粹主义者(我的版本)认为一遍又一遍地重新声明同一个变量是愚蠢的 - 至少在阅读代码时会抛出一个变量。

思考?有问题吗?

5 个答案:

答案 0 :(得分:7)

据我所知,所有局部变量都在方法调用开始时分配了堆栈空间,因此无论是在循环内还是在循环中声明变量都无关紧要。

在这种情况下,我会为可读性编写代码 - 如果你不需要循环外的变量,我个人会在循环中声明它,以使它更接近实际使用它的代码并缩小范围变量尽可能多。

答案 1 :(得分:3)

是的,这是编译器可以执行的最基本的优化之一,适用时

当然,只有在不改变程序语义的情况下,编译器才能执行此操作。

但是,您还缺少另一个重要方面。你假设在循环中声明变量是有代价的。

您认为声明bool类型的变量需要多长时间?它基本上是免费的。编译器不需要做任何事情,除了递增堆栈指针(无论函数在哪里声明变量,都必须这样做),并为它赋值(在你的例子中发生在循环内部)在任何情况下)。

因此性能差异。这是编译器擅长的纯机械优化。它知道如何在堆栈上分配内存,并且知道执行此操作以及初始化或分配给变量的确切成本。

只要有可能,您应该在尽可能小的范围内声明变量。在你需要的时候宣布它们,并且不久。所以你的第一种方法是令人讨厌的,它甚至没有更快,所以它基本上只是一个坏主意。

在C#中,从来没有任何不利因素。变量是一个值类型,在这种情况下分配它是免费的(相同的堆栈空间在整个循环中重用,所以在循环中声明变量没有成本),或者它是一个引用变量(在这种情况下你'只是在堆栈上放置一个引用,出于同样的原因是免费的)

在C ++中,有些情况下它不起作用,或者它有实际成本。变量可能在其构造函数中执行一些无法优化的昂贵操作,然后如果变量在循环内声明,则必须在每次迭代时执行。编译器不能在循环外部移动变量,因为它必须在循环的每次迭代中初始化:这是你,程序员指定的,而实现这一点的唯一方法是调用构造函数。

然后,将对象移出循环可能是值得的。 (但当然,赋值运算符,而不是构造函数,每次迭代都会执行,所以希望这是一个更便宜的操作)

答案 2 :(得分:2)

为1&amp; 2将是相同的。声明变量的位置无关紧要。这是一个示例(Console.WriteLine("Yes")语句中包含if):

.maxstack 2
.locals init (
    [0] bool x,
    [1] int32 i)
L_0000: ldc.i4.0 
L_0001: stloc.1 
L_0002: br.s L_001b
L_0004: call bool ConsoleApplication8.Program::GetSomeValue()
L_0009: stloc.0 
L_000a: ldloc.0 
L_000b: brfalse.s L_0017
L_000d: ldstr "Yes"
L_0012: call void [mscorlib]System.Console::WriteLine(string)
L_0017: ldloc.1 
L_0018: ldc.i4.1 
L_0019: add 
L_001a: stloc.1 
L_001b: ldloc.1 
L_001c: ldc.i4.s 100
L_001e: blt.s L_0004
L_0020: ret 

然而,从风格上讲,我会说你的变量尽可能接近你使用它们的位置。

就性能而言,对于这些示例更重要的是GetSomeValue()调用是否可以移出循环(即它在整个循环中是不变的)。在某些情况下,编译器可以自己检测它。

#3将取决于函数Do2()是否内联到循环中。如果是,它最终可能会产生完全相同的生成代码(不是MSIL)。

答案 3 :(得分:2)

你做的是,你是不是试图比你的工具更聪明,你只是使用它们!获取一个分析器,分析您的代码,并测量性能!然后,只有那样,你会知道它是否是你需要关心的事情。

我知道,这听起来非常直观,以为可能,也许,你可能不会知道你的编译器可能会或可能不会优化的每件事。当然,开发人员(包括经验丰富的人)似乎更常见的是认为他们知道他们不知道的东西,然后以效率的名义编写巨大的不可维护,无法优化的代码块。他们应该做的是确保代码是可维护的,然后确定哪些瓶颈(如果有的话)需要展开。

答案 4 :(得分:0)

可维护性通常更重要,但这个特殊的例子不是死罪。

  • 我认为有趣/典型的是你被告知它更有效率。

如果表现非常重要,那么应该处理表现 Here's an example of the method I use.