我有以下C#代码尝试在发布模式下进行基准测试:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication54
{
class Program
{
static void Main(string[] args)
{
int counter = 0;
var sw = new Stopwatch();
unchecked
{
int sum = 0;
while (true)
{
try
{
if (counter > 20)
throw new Exception("exception");
}
catch
{
}
sw.Restart();
for (int i = 0; i < int.MaxValue; i++)
{
sum += i;
}
counter++;
Console.WriteLine(sw.Elapsed);
}
}
}
}
}
我在64位计算机上安装了VS 2015。当我在32位下运行代码时,它会在 0.6秒周围运行每次迭代,并打印到控制台。当我在64位下运行它时,每次迭代的持续时间只会跳到 4秒!我在我的同事计算机上尝试了示例代码,该计算机只安装了VS 2013。 32位和64位版本都运行 0.6秒。
除此之外,如果我们只删除try catch块,它也会以 0.6秒运行,其中VS 2015为64位。
当有一个try catch块时,这看起来像是一个严重的RyuJIT回归。我是对的吗?
答案 0 :(得分:11)
基准测试是一门艺术。对您的代码进行一些小修改:
Console.WriteLine("{0}", sw.Elapsed, sum);
现在你会发现差异消失了。换句话说,x86版本现在和x64代码一样慢。你可以弄清楚RyuJIT没有做什么传统的抖动从这个微小的变化做了什么,它没有消除不必要的
sum += i;
使用Debug&gt;查看生成的机器代码时可以看到的内容Windows&gt;拆卸。这在RyuJIT确实是一个怪癖。其死代码消除不如传统抖动那么彻底。否则不完全没有理由,微软重写了x64抖动,因为它无法轻易解决的错误。其中一个是优化器的一个相当讨厌的问题,它在优化方法上花费的时间没有上限。导致在具有非常大的物体的方法上行为相当差,它可能会在树林中出现几十毫秒并导致明显的执行暂停。
把它称为bug,呃,不是真的。写出合理的代码和抖动不会让你失望。优化确实永远从通常的位置开始,在程序员的耳朵之间。
答案 1 :(得分:0)
经过一些测试后,我得到了一些有趣的结果。我的测试围绕try catch
块进行。正如OP指出的那样,如果你删除这个块,执行的时间是一样的。我已经进一步缩小了这一点并得出结论,这是因为counter
块中if
语句中的try
变量。
让我们删除多余的throw
:
try
{
if (counter== 0) { }
}
catch
{
}
您将使用此代码获得与原始代码相同的结果。
让更改计数器为实际的int值:
try
{
if (1 == 0) { }
}
catch
{
}
使用此代码,64位版本的执行时间从4秒减少到大约1.7秒。仍然是32位版本的两倍。但我觉得这很有意思。不幸的是,在我快速的谷歌搜索后,我没有提出一个理由,但如果我发现为什么会发生这种情况,我会多挖一点并更新这个答案。
至于我们想要削减64位版本的剩余第二个版本,我可以看到,这只是在sum
循环中将i
增加for
。
让我们改变这一点,以便sum
不超过其界限:
for (int i = 0; i < int.MaxValue; i++)
{
sum ++;
}
此更改(以及try
块中的更改)将使64位应用程序的执行时间减少到0.7秒。我对1秒钟时间差异的推理是由于64位版本需要处理int
的自然方式,这自然是32位。
在32位版本中,有32位分配给Int32(sum
)。当sum
超出其界限时,很容易确定这一事实。
在64位版本中,有64位分配给Int32(sum
)。当总和超过其界限时,需要有一种机制来检测这一点,这可能导致减速。甚至可能添加sum
&amp;由于分配的冗余位增加,i
需要更长的时间。
我在这里理论;所以不要把它当作福音。我以为我会发布我的发现。我相信其他人能够对我找到的问题有所了解。
-
@HansPassant的回答指出sum += i;
行可能被删除,因为它被认为是不必要的,这是完全合理的,sum
没有在for
循环之外使用。在他介绍for循环之外的sum值之后,我们注意到x86版本和x64版本一样慢。所以我决定做一些测试。让我们改变for循环并打印到以下内容:
int x = 0;
for (int i = 0; i < int.MaxValue; i++)
{
sum += i;
x = sum;
}
counter++;
Console.WriteLine(sw.Elapsed + " " + x);
您可以看到我在int x
循环中引入了sum
的新值for
。 x的值不会写入控制台。 sum
不会离开for
循环。不管你信不信,这实际上将x64的执行时间缩短为0.7秒。但是,x86版本会跳跃到1.4秒。