调用类型构造器并不那么简单,JIT编译器不得不决定是否生成调用它的代码,并且CLR要确保调用是线程安全的。当编译器决定发起一个调用来执行类型构造器,它必须判断是否应该这样做,有两种可能性:
1.JIT在创建类型的第一个实例的代码之前立即发起或者在访问类的非继承的字段,成员的代码之前立即调用
2.JIT在首次访问一个静态字段,静态方法,实例方法,或调用一个实例构造器的代码之前某个时间调用,因为CLR要确保静态构造器在其他成员被访问之前运行。
先来看一下代码:
internal sealed class BeforeFieldInit { public static Int32 s_x = 123; } internal sealed class Precise { public static Int32 s_x; static Precise() { s_x = 123; } } class Program { static void Main(string[] args) { const Int32 iterations = 1000 * 1000 * 1000; perfTest1(iterations); perfTest2(iterations); Console.ReadKey(); } private static void perfTest1(Int32 iterations) { Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < iterations; i++) { BeforeFieldInit.s_x = 1; } Console.WriteLine(sw.Elapsed); sw = Stopwatch.StartNew(); for (int i = 0; i < iterations; i++) { Precise.s_x = 1; } Console.WriteLine(sw.Elapsed); } private static void perfTest2(Int32 iterations) { Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < iterations; i++) { BeforeFieldInit.s_x = 1; } Console.WriteLine(sw.Elapsed); sw = Stopwatch.StartNew(); for (int i = 0; i < iterations; i++) { Precise.s_x = 1; } Console.WriteLine(sw.Elapsed); } }
perfTest1方法被JIT编译的时候,BeforeFieldInit和Precise类的类型构造器还没有被执行,所以类型构造器的调用将被嵌入这个方法的代码中,因此执行较慢。
而perfTest2进行JIT编译时,类型构造器已经被执行过了,所以这个方法将不再调用类型构造器,使得运行相对较快。
在perfTest1中,BeforeFieldInit没有显式的定义类型构造器,那么在第一个循环中,JIT编译器优化调用的类型构造器,使得类型构造器的调用在循环之前。Precise显示的定义了类型构造器,在第二个循环中将在每一次循环中调用一次类型构造器,所以perfTest1方法中第一个循环要比第二个循环快一些。