为什么注释掉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]
这是. NET Framework中的一个错误。
嗯,真的,我只是猜测,但我提交了一份错误报告 <罢工> 微软连接 看看他们怎么说。微软删除该报告后,我在GitHub上的roslyn项目上重新提交了它。
更新:微软已将该问题转移到coreclr项目。从对该问题的评论来看,将其称为错误似乎有点强烈;这更像是一个缺失的优化。
我无法与.NET编译器对话,也无法与它的优化对话,甚至无法与它何时执行优化对话。
但是在这种特殊情况下,如果编译器将布尔变量折叠到实际语句中,并且您要尝试调试此代码,则优化后的代码将与编写的代码不匹配。您将无法单步跳过isMulitpleOf16赋值并检查它的值。
这只是优化可能被关闭的一个例子。可能还有其他的。优化可能在代码的加载阶段进行,而不是在 CLR 的代码生成阶段进行。
现代运行时非常复杂,尤其是如果您在运行时加入JIT和动态优化。我很感激代码有时能做到它所说的。
问题应该是“为什么我在我的机器上看到如此大的差异?”。我无法重现如此巨大的速度差异,并怀疑有特定于您的环境的东西。不过,很难判断它是什么。可能是您前一段时间设置的一些(编译器)选项,但忘记了它们。
我创建了一个控制台应用程序,在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块结束时关闭的匿名局部变量不能支持以下操作,有什么原因吗?
问题 你想在使用范围内执行某个代码片段,并且希望在执行后所有的结果都不可见。 解决方案 为了理解这个问题,先试试一个简单场景。首先,在全局命名空间内执行一个代码片段: >>> a = 13 >>> exec('b = a + 1') >>> print(b) 14 >>> 然后,再在一个函数中执行同样的代码: >>> def test(): ... a = 13 ... exec(