2017-08-29 44 views
10

代码:在相同usings执行相同代码块的持续时间如此不同的原因是什么?

internal class Program 
{ 
    private static void Main(string[] args) 
    { 
     const int iterCount = 999999999; 

     var sum1 = 0; 
     var sum2 = 0; 

     using (new Dis()) 
     { 
      var sw = DateTime.Now; 
      for (var i = 0; i < iterCount; i++) 
       sum1 += i; 
      Console.WriteLine(sum1); 
      Console.WriteLine(DateTime.Now - sw); 
     } 

     using (new Dis()) 
     { 
      var sw = DateTime.Now; 
      for (var i = 0; i < iterCount; i++) 
       sum2 += i; 
      Console.WriteLine(sum2); 
      Console.WriteLine(DateTime.Now - sw); 
     } 

     Console.ReadLine(); 
    } 

    private class Dis : IDisposable 
    { 
     public void Dispose(){} 
    } 
} 

两个相同的块。

输出:

2051657985 
00:00:00.3690996 
2051657985 
00:00:02.2640266 

第二块需要2.2秒!但是,如果摆脱使用,持续时间变得相同(约0.3秒,如第一个)。 我试过.net framework 4.5和.net core 1.1,在发布时,结果是一样的。

任何人都可以解释这种行为吗?

+2

也许尝试使用变量而不是'Console.WriteLine'?这可能是问题所在。 – ispiro

+0

当我在linqpad中运行你的代码时,它给我两个代码块约2.5秒。在ideone上运行时,它会给两个块提供〜0.3秒的时间:https://ideone.com/ReOpaH。 – Chris

+2

也使用适当的秒表。你打电话给你的日期时间变量sw,这意味着你想要使用秒表,但是你没有使用秒表。 – Chris

回答

13

您必须查看抖动产生的机器码以了解其根本原因。使用工具>选项>调试>常规>取消压制JIT优化选项。切换到发布版本。在第一个和第二个循环上设置一个断点。当它命中使用调试> Windows>反汇编。

你会看到机器代码的for循环的机构:

    sum1 += i; 
00000035 add   esi,eax 

和:

    sum2 += i; 
000000d9 add   dword ptr [ebp-24h],eax 

或者换句话说,在sum1变量存储在CPU寄存器esi。但sum2变量存储在内存中,方法的堆栈帧。大,很大的差异。寄存器速度非常快,内存很慢。堆栈帧的内存将位于L1缓存中,访问该缓存的现代机器的延迟时间为3个周期。存储缓冲区会很快被大量写入所淹没,导致处理器停顿。

寻找一种方法来保持CPU寄存器中的变量是one of the primary jitter optimization duties。但是这具有局限性,特别是x86几乎没有可用的寄存器。当它们全部用完时,抖动没有选择,只能用内存来代替。请注意,using语句有一个隐藏的局部变量,这就是为什么它有一个效果。

理想情况下,抖动优化器将在如何分配寄存器方面做出更好的选择。将它们用于循环变量(它所做的)和总和变量。提前编译器会得到正确的结果,有足够的时间执行代码分析。但是即时编译器在严格的时间限制下运行。

基本对策是:

  • 分手代码成单独的方法,以便像ESI一个寄存器可以重新使用。
  • 删除抖动强制(项目>属性>生成选项卡>取消选中“首选32位”)。 x64提供了8个额外的寄存器。

最后一颗子弹是有效的传统x64的抖动(面向.NET 3.5使用它),但不能用于x64抖动重写(又名RYuJIT)先在4.6可用。重写是必要的,因为遗留抖动需要花费太多时间优化代码。令人失望的是,RyuJIT确实有令人失望的诀窍,我认为它的优化者可以在这里做得更好。

+0

谢谢,看起来像答案!有趣的是,如果我们将sum2(var sum2 = 0;)的声明移到使用块之间,则在两个循环中都使用寄存器。 –

相关问题