C# 静态构造函数性能以及我们为什么可以';t指定beforefieldinit

C# 静态构造函数性能以及我们为什么可以';t指定beforefieldinit,c#,.net,performance,clr,C#,.net,Performance,Clr,我在使用以下两种结构时遇到了速度上的差异: 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

我在使用以下两种结构时遇到了速度上的差异:

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);
    }
}
编译器为
NoStaticCtor
生成一个静态构造函数,该构造函数与
StaticCtor
中显式声明的构造函数相同。我知道,只有在静态构造函数没有明确定义时,编译器才会发出beforefieldinit

它们产生几乎相同的il代码,除了一个不同之处,即使用beforefieldinit声明结构,这就是我感觉差异所在的地方,因为我知道它决定了何时调用类型构造函数,尽管我不太明白为什么会有这样的差异。它假定不是每次迭代都调用类型构造函数,因为类型构造函数只能调用一次。1

所以

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

2) 为什么编译器设计者a)没有将所有结构
beforefieldinit
设置为默认值,b)没有给开发人员显式指定该行为的能力?当然,这是假设你不能,因为我还没有找到办法


编辑:

一,,基本上,每一个循环都要运行一次,期望有所改进,但这并不多:

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应该知道类型构造函数已经被调用了,而不是在编译第二个循环时发出这样做的代码

除了莫蒂的回答之外:
产生更好的结果,因为JITing的差异,
DoSecondLoop
的JITing不会发出静态ctor检查,因为它检测到它以前在
DoFirstLoop
中执行过,导致每个循环以相同的速度执行。(~3秒)

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

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

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

问题是,如果检查静态ctor的jitted代码在循环中,那么该测试将在每次迭代中进行

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

C#编译器会自动为我们决定何时发出此属性。当前(C#4)仅当代码未显式定义静态构造函数时才会发出它。其背后的想法是,如果您自己定义了静态ctor,那么时间对您来说可能更重要,并且ststic ctor不应该提前执行。这可能是真的,也可能不是真的,但您无法更改此行为

以下是我的在线.NET教程中详细解释这一点的部分的链接:

顺便说一句,我不建议在结构上使用静态构造函数,因为信不信由你,它们不能保证执行!这不是问题的一部分,所以我不会详细说明,但是如果你感兴趣的话,请看下面的细节:(我在视频的2:30左右触及这个主题)


我希望这有帮助

这里需要一些关于大数字的观点。你测量的开销是一纳秒。是的,这就是测试+跳转指令所需要的。@Hans我知道开销很小。我从来不会在生产中编写这样的代码。我目前正在“CLR是如何工作的”这段时间里,我一直在处理一些不寻常的事情。我只是想理解为什么JITer会根据编译器发出的属性做出决定。我可能应该买本书。不要在结构上使用静态构造函数是一个很好的建议。我非常清楚CLR可能并不总是执行它。现在,当我在第一次编辑中考虑到pastebin链接时,JITer将静态ctor检查放在涉及
StaticCtor.GetMyValue()
的两个循环中,而不是仅放在第一个for循环中,即使JITer知道它在代码中的前面放置了检查。本质上,在任何尚未调用静态ctor的函数中,它将在每次访问之前进行检查,而不仅仅是在第一次访问之前,对吗?嗯,正如您所说,通过测试,JIT编译器可以决定是否将测试放在每个JIT方法的基础上。我将您的代码修改为每次调用两个单独的方法(带和不带ctor),并第二次获得了性能提升:。我不确定这是文档化的还是实现细节,JIT编译器的任何更新都可能会改变。另外,我相信您也知道,JIT编译器的实现在不同平台之间可能有所不同。是的,我只是觉得它很有趣。正如我在上面所评论的,我正处于一个阶段,我只想搞乱/理解CLR,这种行为突然出现了。我被JIT是如何决定什么时候做什么的迷住了。谢谢!:)@ChristopherCurrens&MottiShaked,为什么字段是一个结构很重要?它不重要。这同样适用于类。
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 . . .