2012-07-13 96 views
7

我注意到启动时间会因此而有所不同,因此我放置了一段初始化代码。我觉得这很奇怪,所以我写了一个小基准,证实了我的怀疑。看来在调用main方法之前执行的代码比正常情况慢。静态构造函数中的代码运行速度较慢

为什么Benchmark();以不同的速度运行,取决于在常规代码路径之前和之后调用?

这里的基准代码:

class Program { 
    static Stopwatch stopwatch = new Stopwatch(); 
    static Program program = new Program(); 

    static void Main() { 
     Console.WriteLine("main method:"); 
     Benchmark(); 
     Console.WriteLine(); 

     new Program(); 
    } 

    static Program() { 
     Console.WriteLine("static constructor:"); 
     Benchmark(); 
     Console.WriteLine(); 
    } 

    public Program() { 
     Console.WriteLine("public constructor:"); 
     Benchmark(); 
     Console.WriteLine(); 
    } 

    static void Benchmark() { 
     for (int t = 0; t < 5; t++) { 
      stopwatch.Reset(); 
      stopwatch.Start(); 
      for (int i = 0; i < 1000000; i++) 
       IsPrime(2 * i + 1); 
      stopwatch.Stop(); 
      Console.WriteLine(stopwatch.ElapsedMilliseconds + " ms"); 
     } 
    } 

    static Boolean IsPrime(int x) { 
     if ((x & 1) == 0) 
      return x == 2; 
     if (x < 2) 
      return false; 
     for (int i = 3, s = (int)Math.Sqrt(x); i <= s; i += 2) 
      if (x % i == 0) 
       return false; 
     return true; 
    } 
} 

结果表明:Benchmark()运行几乎两倍两个静态构造函数和构造函数static Program program财产慢:

// static Program program = new Program() 
public constructor: 
894 ms 
895 ms 
887 ms 
884 ms 
883 ms 

static constructor: 
880 ms 
872 ms 
876 ms 
876 ms 
872 ms 

main method: 
426 ms 
428 ms 
426 ms 
426 ms 
426 ms 

// new Program() in Main() 
public constructor: 
426 ms 
427 ms 
426 ms 
426 ms 
426 ms 

倍增迭代的次数基准循环会使所有时间翻倍,这表明所发生的性能损失不是恒定的,而是一个因素。

// static Program program = new Program() 
public constructor: 
2039 ms 
2024 ms 
2020 ms 
2019 ms 
2013 ms 

static constructor: 
2019 ms 
2028 ms 
2019 ms 
2021 ms 
2020 ms 

main method: 
1120 ms 
1120 ms 
1119 ms 
1120 ms 
1120 ms 

// new Program() in Main() 
public constructor: 
1120 ms 
1128 ms 
1124 ms 
1120 ms 
1122 ms 

为什么会这样呢?这将是有意义的,如果初始化如果它完成它所属的地方一样快。测试是在.NET 4,发布模式,优化。

+2

究竟是什么问题? – jcolebrand 2012-07-13 03:17:08

+0

编译设置?框架版本? – user7116 2012-07-13 03:24:41

+0

请看看我的编辑是否让你感觉(我不知道为什么),试着在.Net 4/release上得到类似的结果。 – 2012-07-13 03:52:17

回答

3

这是一个非常有趣的问题。我花了一些时间试验你的程序的变体。这里有几点看法:

  1. 如果将基准()静态方法分成不同的类,对于静态构造函数的性能损失消失。

  2. 如果您将Benchmark()方法变为实例方法,则性能损失将消失。当我分析你的快速病例(1,2)和慢速病例(3,4)时,慢速病例花费了额外的时间在CLR帮助程序方法中,特别是JIT_GetSharedNonGCStaticBase_Helper。

基于这些信息,我可以推测发生了什么。 CLR需要确保每个静态构造函数至多执行一次。一个复杂的情况是静态构造函数可能会形成一个循环(例如,如果A类包含B类型的静态字段而B类包含A类型的静态字段)。

在静态构造函数内部执行时,JIT编译器会插入一些静态方法调用的检查,以防止由于循环类依赖关系而导致潜在的无限循环。一旦从静态构造函数外部调用静态方法,CLR将重新编译该方法以除去检查。

这应该是非常接近正在发生的事情。

+0

感谢您更多地关注此问题,尤其是分析。虽然我不确定你的分析。这意味着我的测试会有不断的开销。我已更新我的问题,以表明性能损失似乎是一个因素。 – Zong 2012-07-15 15:02:42

+0

每个IsPrime()调用**都有一个额外的开销**,因为每个IsPrime调用都必须被检查包围。例如,如果您使IsPrime更昂贵(例如,调用IsPrime(1000000000 + 2 * i)),则四种情况之间的差异消失。 – 2012-07-15 18:21:22

+0

即,如果将迭代次数加倍,则还会使IsPrime调用次数加倍,因此会使检查次数增加一倍。 – 2012-07-15 18:23:30

3

这是一个非常有据可查的事实。

静态构造函数很慢。 .net运行时不够智能来优化它们。

参考:Performance penalty of static constructors

明确的静态构造函数是昂贵的,因为他们 要求运行,以确保该值设置完全相同 被访问类的任何成员之前。确切的成本是 取决于情况,但在某些情况下它可能非常明显。

+0

我不认为链接的文章适用。文章说,带有静态构造函数的类型在beforeFieldInit标记,因此使用该类型会导致额外的运行时成本。但是,OP没有比较两种类型,一种是静态构造函数,另一种是没有。整个基准测试只有一种类型,并且该类型具有静态构造函数。所以,其他事情正在发生。 – 2012-07-13 06:40:16

+0

你是对的;我的基准实际上表明文章中两个案例的表现或多或少是相同的。 – Zong 2012-07-15 14:56:09