博客园  :: 首页  :: 联系 :: 订阅 订阅  :: 管理

关于静态构造函数和BeforeFieldInit

Posted on 2009-09-08 20:33  生鱼片  阅读(1346)  评论(4编辑  收藏  举报

1.看下面的例子:

public static class MyClass<T>

{
    public static readonly DateTime Time = GetNow();
    private static DateTime GetNow()
    {
        Console.WriteLine("GetNow execute!");
        return DateTime.Now;
    }
}

class Program
{
    static void Main(string[] args)
    {

        Console.WriteLine("Main execute!");
        Console.WriteLine("int: " + MyClass<int>.Time);
        Thread.Sleep(3000);
        Console.WriteLine("string: " + MyClass<string>.Time);
        Console.ReadLine();
    }
}

结果如下:

GetNow execute!

GetNow execute!

Main execute!

int: 2009/9/8 15:34:31

string: 2009/9/8 15:34:31

看上面的结果在Main函数执行之前GetNow就执行了,就取到了DateTime.Now,所以输出的时间是一样的。

2.我们在上面的MyClass中加一个静态的构造函数我们在来看结果:

public static class MyClass<T>

{
    public static readonly DateTime Time = GetNow();
    private static DateTime GetNow()
    {

        Console.WriteLine("GetNow execute!");
        return DateTime.Now;
    }
    static MyClass() { }
}

结果如下:

Main execute!

GetNow execute!

int: 2009/9/8 15:40:12

GetNow execute!

string: 2009/9/8 15:40:15

我们可以发现每次的时间不同了。出现这种现象是由于当类没有静态构造函数的时候。在il中该类会被标记为BeforeFieldInit,这个是由编译器自动完成的。没有静态构造函数的时候初始化在刚进入方法的时候就发生了,而有静态函数的时候而且我们不需要做任何动作,只要有就可以,这个时候静态初始化在使用前才发生.我们可以通过看IL代码来证实这种现象,如下:

clip_image002

3.使用BeforeFieldInit会提高性能,下面我们就测试下,在测试我们需要计算代码执行时间,我们就是用老赵的组件,我稍稍做了一点修改,因为老赵用的win32 API是vista下的,为了以后查询方便,也贴下代码:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Diagnostics;

using System.Threading;

using System.Runtime.InteropServices; 

namespace CSharpDemo

{
    public static class CodeTimer

    {

        public static void Initialize()

        {

            Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

            Thread.CurrentThread.Priority = ThreadPriority.Highest;

            Time("", 1, () => { });

        } 

        public static void Time(string name, int iteration, Action action)

        {

            if (String.IsNullOrEmpty(name)) return;

            // 1.

            ConsoleColor currentForeColor = Console.ForegroundColor;

            Console.ForegroundColor = ConsoleColor.Yellow;

            Console.WriteLine(name); 

            // 2.

            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);

            int[] gcCounts = new int[GC.MaxGeneration + 1];

            for (int i = 0; i <= GC.MaxGeneration; i++)

            {
                gcCounts[i] = GC.CollectionCount(i);

            } 

            // 3.

            Stopwatch watch = new Stopwatch();

            watch.Start();

            long cycleCount = GetCycleCount();

            for (int i = 0; i < iteration; i++) action();

            long cpuCycles = GetCycleCount() - cycleCount;

            watch.Stop(); 

            // 4.

            Console.ForegroundColor = currentForeColor;

            Console.WriteLine("\tTime Elapsed:\t" + watch.ElapsedMilliseconds.ToString("N0") + "ms");

            Console.WriteLine("\tCPU Cycles:\t" + cpuCycles.ToString("N0")); 

            // 5.

            for (int i = 0; i <= GC.MaxGeneration; i++)

            {
                int count = GC.CollectionCount(i) - gcCounts[i];
                Console.WriteLine("\tGen " + i + ": \t\t" + count);
            } 

            Console.WriteLine();

        } 

        private static long GetCycleCount()

        {
            long l;

            long kernelTime, userTimer;

            GetThreadTimes(GetCurrentThread(), out l, out l, out kernelTime, out userTimer);

            return kernelTime + userTimer; 

        } 

        [DllImport("kernel32.dll", SetLastError = true)]

        static extern bool GetThreadTimes(IntPtr hThread, out long lpCreationTime,

           out long lpExitTime, out long lpKernelTime, out long lpUserTime); 

        [DllImport("kernel32.dll")]

        static extern IntPtr GetCurrentThread(); 

    }

}

下面我们开始测试,我们准备两个类:

public class MarkBeforeFieldInit

{

    public static string test;

} 

public class NoBeforeFieldInit

{
    public static string test;
    static NoBeforeFieldInit()

    {       

    }
}

测试代码如下:

class Program

{

    static void Main(string[] args)

    {
        CodeTimer.Initialize();
        int iteration = 1000 * 1000*1000; 

        CodeTimer.Time("MarkBeforeFieldInit", iteration, () => { MarkBeforeFieldInit.test = "test"; });

        CodeTimer.Time("NoBeforeFieldInit", iteration, () => { NoBeforeFieldInit.test= "test"; }); 

        CodeTimer.Time("MarkBeforeFieldInit2", iteration, () => { MarkBeforeFieldInit.test = "test"; });

        CodeTimer.Time("NoBeforeFieldInit2", iteration, () => { NoBeforeFieldInit.test = "test"; });     

    }
}

结果如下:

clip_image002[5]

可以看出BeforeFieldInit方式的执行速度确实快,但为什么第二次执行的速度差不多呢?因为经过第一次执行后JIT编译器知道类型的构造器已经被调用了,所以第二次执行时不会显示对构造函数进行调用。