2011-11-16 95 views
6

我碰到一个差排在速度使用以下两种结构:静态构造函数的性能和为什么我们不能指定beforefieldinit

public struct NoStaticCtor 
{ 
    private static int _myValue = 3; 
    public static int GetMyValue() { return _myValue; } 
} 

public struct StaticCtor 
{ 
    private static int _myValue; 
    public static int GetMyValue() { return _myValue; } 
    static StaticCtor() 
    { 
     _myValue = 3; 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     long numTimes = 5000000000; // yup, 5 billion 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     for (long i = 0; i < numTimes; i++) 
     { 
      NoStaticCtor.GetMyValue(); 
     } 
     sw.Stop(); 
     Console.WriteLine("No static ctor: {0}", sw.Elapsed); 

     sw.Restart(); 
     for (long i = 0; i < numTimes; i++) 
     { 
      StaticCtor.GetMyValue(); 
     } 
     sw.Stop(); 
     Console.WriteLine("with static ctor: {0}", sw.Elapsed); 
    } 
} 

其产生的结果:

Release (x86), no debugger attached: 
No static ctor: 00:00:05.1111786 
with static ctor: 00:00:09.9502592 

Release (x64), no debugger attached: 
No static ctor: 00:00:03.2595979 
with static ctor: 00:00:14.5922220 

编译器产生NoStaticCtor的静态构造函数与StaticCtor中明确声明的相同。我明白,当静态构造函数没有明确定义时,编译器只会发出beforefieldinit

他们生产几乎相同的IL代码,但有一个区别,宣布与beforefieldinit的结构,这是我觉得不同之处在于,因为我知道它确定何时类型构造函数被调用,虽然我不是很弄清楚为什么会有这样的差异。它假定它不是每次迭代都调用类型构造函数,因为一个类型构造函数只能被调用一次。

所以,

1)为什么用beforefieldinit的结构和没有一个之间的时间差? (我想JITer在for循环中做了一些额外的工作,但是,我不知道如何查看JITer的输出以查看内容。)

2)为什么编译器设计者a)没有使所有结构体beforefieldinit是默认的,并且b)不给开发者明确指定该行为的能力?当然,这个假设你不能,因为我一直没有找到办法。


编辑:

I modified the code,第二次运行基本上每个循环,期待改进,但它并没有太多:

No static ctor: 00:00:03.3342359 
with static ctor: 00:00:14.6139917 
No static ctor: 00:00:03.2229995 
with static ctor: 00:00:12.9524860 
Press any key to continue . . . 

我这样做是因为我虽然很好,也许,但不太可能是,在JITer竟是每次迭代调用类型构造函数。在我看来,JITer会知道类型构造函数已经被调用,并且不会在编译第二个循环时发出代码。

除了Motti的回答是: This code产生更好的效果,因为在JIT编译不同之处,DoSecondLoop的JIT编译不发出静态构造函数检查,因为它检测到它是在DoFirstLoop以前那样,使每个循环执行以相同的速度。 (〜3秒)

+2

这里需要大量的一些观点。您测量的开销是* 1纳秒*。是的,这是关于测试+跳转指令的作用。 –

+0

@Hans我知道开销很小。我从来没有真正写过这样的代码在生产中使用。我目前正在进行“CLR工作如何”的工作,我一直在讨论不寻常的事情。我真的只是想知道为什么JITer根据编译器发出的属性来做出它所做的决定。我应该可以买一本书。 –

回答

10

第一次访问类型时,必须执行静态ctor(无论是显式生成还是隐式生成)。

当JIT编译器将IL编译为本机指令时,它会检查该类型的静态ctor是否已经执行,如果没有发出检查静态ctor是否已被执行的本地代码,那么执行该代码。

jitted代码被缓存以便将来调用相同的方法(这就是为什么代码必须再次检查静态ctor是否被执行)。

问题是,如果检查静态ctor的jitted代码处于循环中,则此测试将在每次迭代中发生。

当beforefieldinit存在时,JIT编译器可以通过在进入循环之前测试静态ctor调用来优化代码。如果不存在,则不允许此优化。

C#编译器会自动决定何时发出此属性。它目前(C#4)只有在代码没有明确定义静态构造函数时才会发出它。其背后的想法是,如果你自己定义了一个静态ctor,那么对你而言,时机可能更重要,而且strt ctor不应该提前执行。这可能会也可能不会,但是你不能改变这种行为。

下面是在详细讲解这个我在网上.NET教程的部分链接:http://motti.me/c1L

BTW,我不建议使用的结构静态构建函数,因为无论你相信与否,他们都不能保证执行!这不是问题的一部分,所以我不会详细说明,但如果您感兴趣,请参阅以下内容以获得更多详细信息:http://motti.me/c1I(我在大约2点30分触及该视频的主题)。

我希望这有助于!

+0

不在结构上使用静态ctors是很好的建议。我非常了解CLR如何不能总是执行它。现在,当我在第一次编辑中考虑到pastebin链接时,即使JITer知道JITer将static静态ctor检查放入包含StaticCtor.GetMyValue()的*两个*循环中,而不是仅在第一个for循环中它先在代码中放置检查。从本质上讲,在静态ctor尚未被调用的任何函数中,它将在* EVERY *访问之前将该检查放在第一个,这是否正确? –

+0

嗯,通过测试,似乎正如你所说,JIT编译器决定是否将测试放在每个连接方法的基础上。我将每次调用的代码修改为两个独立的方法(使用&不使用ctor),并且第二次获得了性能提升:http://pastebin.com/WyQWz5ar。我不确定这是记录还是实现细节,在JIT编译器的任何更新中都可能会发生变化。另外,正如我相信你知道的那样,JIT编译器的实现可能在不同平台之间有所不同。 –

+0

是的,我发现它很有趣。正如我上面所评论的,我处于一个只想混淆/理解CLR的阶段,并且这种行为出现了。我对JIT如何决定何时以及如何去做着迷。谢谢! :) –

相关问题