C# 64位以下执行速度慢。可能是RyuJIT错误?
我有以下C#代码试图在发布模式下进行基准测试:C# 64位以下执行速度慢。可能是RyuJIT错误?,c#,performance,visual-studio-2015,clr,ryujit,C#,Performance,Visual Studio 2015,Clr,Ryujit,我有以下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
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);
}
}
}
}
}
使用系统;
使用System.Collections.Generic;
使用系统诊断;
使用System.Linq;
使用系统文本;
使用System.Threading.Tasks;
命名空间控制台应用程序54
{
班级计划
{
静态void Main(字符串[]参数)
{
int计数器=0;
var sw=新秒表();
未经检查
{
整数和=0;
while(true)
{
尝试
{
如果(计数器>20)
抛出新异常(“异常”);
}
抓住
{
}
sw.Restart();
对于(int i=0;i
我在64位机器上,安装了VS 2015。当我在32位下运行代码时,它会在0.6秒左右运行每个迭代,并打印到控制台。当我在64位下运行它时,每次迭代的持续时间就会跳到4秒!我在我同事的计算机上尝试了示例代码,该计算机只安装了VS 2013。32位和64位版本都在0.6秒左右运行
除此之外,如果我们只删除try-catch块,它也将在0.6秒内运行,VS 2015为64位
当出现try-catch块时,这看起来像是一个严重的RyuJIT回归。我说得对吗?标杆是一门艺术。对代码进行一个小的修改:
Console.WriteLine("{0}", sw.Elapsed, sum);
现在你会看到差异消失了。或者换句话说,x86版本现在和x64代码一样慢。您可能会发现RyuJIT没有做什么,而遗留抖动在这个小改动中做了什么,它没有消除不必要的错误
sum += i;
当您通过“调试”>“Windows”>“反汇编”查看生成的机器代码时,可以看到一些内容。这在龙井确实是个怪癖。它的死代码消除没有遗留抖动那么彻底。除此之外,微软还重写了x64抖动,原因是它无法轻松修复错误。其中之一是优化器的一个相当棘手的问题,它对优化方法所花费的时间没有上限。它会导致具有非常大实体的方法的行为非常糟糕,可能会在林中停留几十毫秒,并导致明显的执行暂停
叫它虫子,嗯,不是真的。编写合理的代码,抖动不会让你失望。优化永远从程序员的耳朵之间的通常位置开始。经过一点测试,我得到了一些有趣的结果。我的测试围绕着
try-catch
块进行。正如OP所指出的,如果删除这个块,执行的时间是相同的。我进一步缩小了范围,并得出结论,这是因为try
块中if
语句中的counter
变量造成的
让我们删除冗余的抛出
:
try
{
if (counter== 0) { }
}
catch
{
}
使用此代码将获得与使用原始代码相同的结果
允许将计数器更改为实际的int值:
try
{
if (1 == 0) { }
}
catch
{
}
使用此代码,64位版本的执行时间从4秒减少到1.7秒左右。仍然是32位版本的两倍。不过我觉得这很有趣。不幸的是,在我快速的谷歌搜索之后,我还没有找到一个原因,但如果我发现了为什么会发生这种情况,我会进一步挖掘并更新这个答案
至于剩下的那一秒,我们想删掉64位版本,我可以看出这是因为在for
循环中,将和增加I
。
让我们对此进行更改,以便总和不超过其界限:
for (int i = 0; i < int.MaxValue; i++)
{
sum ++;
}
您可以看到,我引入了一个新的intx
,它在for
循环中被赋值为sum
。x的值没有写入控制台sum
不会离开for
循环。信不信由你,这实际上将x64的执行时间减少到了0.7秒。然而,x86版本会跳到1.4秒。您的计算机是超级天才!对我来说,每次迭代大约需要10秒:(这里没有区别。32位和64位都给出了非常相同的结果。@M.kazem。我认为这是不可能的。我的计算机是一台Surface Pro 3 i7,具有U级CPU。它绝对不是一个发电站。你确定你在发布模式下运行它,并且不进行调试就启动了吗?顺便说一句,我到目前为止尝试了4台不同的计算机。哦,不,我得到了。因为你需要解决方案属性中的bled选项Optimize code
。现在我得到32位的1.3s
,64位的3.9s
。@M.kazemAkhgary,现在尝试删除try-catch块:)是的。没错。去检查编译后的代码。不管sum
是否超出其界限-它在未经检查的上下文中运行,所以溢出被忽略-如果代码正在运行。正如Hans指出的,由于sum
变量在这个循环之后不会被读取,这是可能的(x86,旧的x64 JIT)完全消除循环作为一种优化。@Damien_不相信者你实际上提出了一个非常有趣的观点。。让我用我的发现更新我的答案。@Damien_不相信者我不认为这是未检查的事实实际上很重要。这只意味着当总和达到b时,它肯定不会抛出溢出异常ounds。(与将其更改为checked不同)。无论如何,它仍然需要对64位的边界执行人工检查,因为int有64位分配给它。理论上我猜。再说一次,我不是这些方面的专家,这只是让我感兴趣。
int x = 0;
for (int i = 0; i < int.MaxValue; i++)
{
sum += i;
x = sum;
}
counter++;
Console.WriteLine(sw.Elapsed + " " + x);