当前位置: 首页 > 知识库问答 >
问题:

为什么添加局部变量?NET代码更慢

巫马自明
2023-03-14

为什么注释掉for循环的前两行并取消注释第三行会导致42%的加速?

int count = 0;
for (uint i = 0; i < 1000000000; ++i) {
    var isMultipleOf16 = i % 16 == 0;
    count += isMultipleOf16 ? 1 : 0;
    //count += i % 16 == 0 ? 1 : 0;
}

在时间的背后是非常不同的汇编代码:循环中的13条和7条指令。该平台运行的是视窗7。NET 4.0 x64。代码优化已启用,测试应用程序在VS2010之外运行。[更新:重现项目,用于验证项目设置。]

消除中间布尔值是一个基本的优化,是我1980年代龙书时代最简单的优化之一。在生成 CIL 或 JITing x64 机器代码时,优化是如何没有被应用的?

是否有一个“真的编译器,我想让你优化这段代码,请”的开关?虽然我很同情过早优化类似于对金钱的热爱这一观点,但我可以看到在尝试分析一个复杂算法时的挫折感,该算法在整个例程中都存在类似的问题。您可以浏览热点,但没有任何迹象表明,通过手动调整编译器中我们通常认为理所当然的内容,可以大大改善更广泛的暖区。我真希望我在这里错过了什么。

更新:x86的速度也存在差异,但这取决于方法被实时编译的顺序。看看JIT顺序为什么会影响性能?

装配代码(根据要求):

    var isMultipleOf16 = i % 16 == 0;
00000037  mov         eax,edx 
00000039  and         eax,0Fh 
0000003c  xor         ecx,ecx 
0000003e  test        eax,eax 
00000040  sete        cl 
    count += isMultipleOf16 ? 1 : 0;
00000043  movzx       eax,cl 
00000046  test        eax,eax 
00000048  jne         0000000000000050 
0000004a  xor         eax,eax 
0000004c  jmp         0000000000000055 
0000004e  xchg        ax,ax 
00000050  mov         eax,1 
00000055  lea         r8d,[rbx+rax] 
    count += i % 16 == 0 ? 1 : 0;
00000037  mov         eax,ecx 
00000039  and         eax,0Fh 
0000003c  je          0000000000000042 
0000003e  xor         eax,eax 
00000040  jmp         0000000000000047 
00000042  mov         eax,1 
00000047  lea         edx,[rbx+rax] 

共有3个答案

聂和宜
2023-03-14

这是. NET Framework中的一个错误。

嗯,真的,我只是猜测,但我提交了一份错误报告 <罢工> 微软连接 看看他们怎么说。微软删除该报告后,我在GitHub上的roslyn项目上重新提交了它。

更新:微软已将该问题转移到coreclr项目。从对该问题的评论来看,将其称为错误似乎有点强烈;这更像是一个缺失的优化。

齐乐
2023-03-14

我无法与.NET编译器对话,也无法与它的优化对话,甚至无法与它何时执行优化对话。

但是在这种特殊情况下,如果编译器将布尔变量折叠到实际语句中,并且您要尝试调试此代码,则优化后的代码将与编写的代码不匹配。您将无法单步跳过isMulitpleOf16赋值并检查它的值。

这只是优化可能被关闭的一个例子。可能还有其他的。优化可能在代码的加载阶段进行,而不是在 CLR 的代码生成阶段进行。

现代运行时非常复杂,尤其是如果您在运行时加入JIT和动态优化。我很感激代码有时能做到它所说的。

胡飞鹏
2023-03-14

问题应该是“为什么我在我的机器上看到如此大的差异?”。我无法重现如此巨大的速度差异,并怀疑有特定于您的环境的东西。不过,很难判断它是什么。可能是您前一段时间设置的一些(编译器)选项,但忘记了它们。

我创建了一个控制台应用程序,在Release模式(x86)下重建并在VS之外运行。两种方法的结果几乎相同,1.77秒。下面是确切的代码

static void Main(string[] args)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    int count = 0;

    for (uint i = 0; i < 1000000000; ++i)
    {
        // 1st method
        var isMultipleOf16 = i % 16 == 0;
        count += isMultipleOf16 ? 1 : 0;

        // 2nd method
        //count += i % 16 == 0 ? 1 : 0;
    }

    sw.Stop();
    Console.WriteLine(string.Format("Ellapsed {0}, count {1}", sw.Elapsed, count));
    Console.ReadKey();
}

请任何有5分钟时间的人复制代码,重建,在VS之外运行,并在评论中发布结果到这个答案。我想避免说“它在我的机器上工作”。

编辑

可以肯定的是,我已经创建了一个64位Winform应用程序,结果与问题中的结果相似——第一种方法比第二种方法慢(1.57秒)。我观察到的差异是33%——仍然很大。似乎有一个错误。NET4 64位即时编译器。

 类似资料:
  • 问题内容: 我已经修改了一些Go代码,以解决与我姐夫玩的电子游戏有关的我的好奇心。 本质上,下面的代码模拟了游戏中与怪物的互动,以及他期望他们在失败后掉落物品的频率。我遇到的问题是,我希望这样的一段代码非常适合并行化,但是当我并发添加时,完成所有模拟所花费的时间往往会使原始代码的速度降低4-6倍没有并发。 为了使您更好地理解代码的工作方式,我有三个主要功能:交互功能,它是玩家和怪物之间的简单交互。

  • 问题内容: 在C / C ++中,我们使用静态局部变量来维护方法的状态。但是,为什么Java不支持它呢? 是的,我可以为此使用一个静态字段。但是创建一个仅维护一个方法状态的字段有点奇怪吗? 问题答案: 您已经找到了唯一的解决方案。 Java放弃了C ++的许多复杂性,这就是其中之一。 作用于函数的静态变量并发地对您造成麻烦(例如,正是由于这个原因,strtok是与pthread一起使用的著名的讨厌

  • 问题内容: 给定以下示例,为什么在所有情况下都未定义? 为什么undefined在所有这些示例中都输出?我不需要解决方法,我想知道为什么会这样。 注意:这是JavaScript异步性的典型问题。随时改进此问题,并添加更多简化的示例,社区可以识别。 问题答案: 一句话回答:异步性。 问题的答案 让我们首先跟踪常见行为。在所有示例中,都在函数内部修改了。该函数显然不会立即执行,而是被分配或作为参数传递

  • 问题内容: Java的设计者是否有任何理由认为不应为局部变量提供默认值?认真地讲,如果实例变量可以被赋予默认值,那为什么我们不能对局部变量做同样的事情呢? 问题答案: 声明局部变量主要是为了进行一些计算。因此,程序员决定设置变量的值,并且不应采用默认值。如果程序员错误地没有初始化局部变量并且使用默认值,则输出可能是一些意外值。因此,在使用局部变量的情况下,编译器将要求程序员在访问变量之前使用一些值

  • 关于我的问题java.util.concurrent.locks.lock的可自动关闭包装中的任何风险,我想知道为什么try-with-resource-语句需要一个命名的局部变量。 我目前的使用情况如下: 变量在try块内未使用,只会污染名称空间。据我所知,类似的C#-语句不需要本地命名变量。 在try块结束时关闭的匿名局部变量不能支持以下操作,有什么原因吗?

  • 主要内容:局部变量,全局变量,局部变量和全局变量的综合示例在《 C语言形参和实参的区别》中提到,形参变量要等到函数被调用时才分配内存,调用结束后立即释放内存。这说明形参变量的作用域非常有限,只能在函数内部使用,离开该函数就无效了。 所谓 作用域( Scope ) ,就是变量的有效范围。 不仅对于形参变量,C语言中所有的变量都有自己的作用域。决定变量作用域的是变量的定义位置。 局部变量 定义在函数内部的变量称为 局部变量(Local Variable) ,