静态构造函数的性能以及为什么我们无法指定beforefieldinit

Chr*_*ens 7 .net c# clr performance

我使用以下两个结构来发现速度上的差异:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

产生结果:

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
Run Code Online (Sandbox Code Playgroud)

编译器生成一个静态构造函数,该构造函数NoStaticCtor与显式声明的构造函数相同StaticCtor.我知道编译器只会beforefieldinit在没有显式定义静态构造函数时才会发出.

它们产生几乎相同的il代码,除了一个区别,声明结构beforefieldinit,我觉得差别在于,因为我知道它确定何时调用类型构造函数,虽然我不能完全弄清楚为什么会有这样的差异.它假设它不是每次迭代都调用类型构造函数,因为类型构造函数只能被调用一次.1

所以,

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

2)为什么编译器设计者a)不使所有结构beforefieldinit都是默认的; b)不让开发人员能够明确指定该行为?当然,这假设你不能,因为我还没有找到办法.


编辑:

1.我修改了代码,基本上是第二次运行每个循环,期待一个改进,但它并不多:

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 . . .
Run Code Online (Sandbox Code Playgroud)

我之所以这样做是因为我可能,不管多么不可能,JITer实际上每次迭代都会调用类型构造函数.在我看来,JITer会知道类型构造函数已被调用,并且在编译第二个循环时不会发出代码来执行此操作.

除了Motti的答案: 这段代码产生更好的结果,因为JITing的差异,JITing DoSecondLoop不会发出静态ctor检查,因为它检测到它是先前完成的DoFirstLoop,导致每个循环以相同的速度执行.(~3秒)

Mot*_*ked 11

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

当JIT编译器将IL编译为本机指令时,它会检查该类型的静态ctor是否已执行,如果不是,则发出本机代码(再次)检查静态ctor是否已执行,如果不执行,则执行它.

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

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

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

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

这是我在线.NET教程部分的链接,详细解释了这一点:http://motti.me/c1L

顺便说一句,我不建议在结构上使用静态ctors,因为信不信由你,它们不能保证执行!这不是问题的一部分,所以我不会详细说明,但如果你感兴趣的话,可以看看这个更详细的信息:http://motti.me/c1I (我在2:30左右触摸了主题进入视频).

我希望这有帮助!