静态构造函数会导致性能开销吗?

时间:2010-05-27 14:09:06

标签: c# .net static constructor

最近在dotnetpearls.com上发表的一篇文章here中说,静态因素会导致大量的性能下降。

无法理解为什么?

4 个答案:

答案 0 :(得分:18)

我认为“实质性”在大多数用例中都是夸大其词。

由于beforefieldinit flag的存在/不存在,具有静态构造函数(即使它什么也不做)会影响类型初始化时间。当你有一个静态构造函数时,有更严格的保证。

对于大多数代码,我建议这没有太大区别 - 但如果你是紧密循环并访问类的静态成员,它可能会。就个人而言,我不会太担心 - 如果你怀疑它与你的真正的应用程序有关,那么测试它而不是猜测。微观标记极有可能夸大这里的效果。

值得注意的是.NET 4 behaves somewhat differently to previous versions在进行类型初始化时 - 所以任何基准测试都应该真正显示不同版本以便相关。

答案 1 :(得分:11)

我刚刚复制了他的测试。

对于带有 DEBUG 构建的1000000000次迭代,我得到:

  • 4s用于静态类的静态 构造
  • 3.6s相同的类,带有注释掉的静态构造函数
  • 2.9s,类非静态(并在之前创建一个实例) 迭代)与静态 建设者与否

RELEASE 构建相同突出显示差异:

  • 静态构造函数的静态类: 4046.875ms
  • 没有静态构造函数的静态类:484.375ms
  • 使用静态构造函数的实例: 484.375ms
  • 没有静态构造函数的实例:484.375ms

答案 2 :(得分:6)

CLR为静态构造函数的执行提供了非常强大的保证,它承诺只调用它们一次,并且之前类中的任何方法都可以运行。当有多个线程使用该类时,这种保证是非常棘手的。

看一下SSCLI20的CLR源代码,我看到了相当大的代码块,专门用于提供这种保证。它维护一个运行静态构造函数的列表,受全局锁保护。一旦它在该列表中获得一个条目,它就会切换到一个特定于类的锁,以确保没有两个线程可以运行构造函数。对状态位进行双重检查锁定,指示构造函数已在运行。许多提供异常保证的不可思议的代码。

嗯,这段代码不是免费的。将它添加到cctor本身的执行时间,你会看到一些开销。一如既往,不要让这个抽筋你的风格,这个保证也是一个非常好的,你不想自己提供。在修复之前进行测量。

答案 3 :(得分:-1)

我刚做了一个小测试来检查在我的一个类中添加静态构造函数的影响。

我有一个基类,如下所示:

public abstract class Base
{
    public abstract Task DoStuffAsync();
}

问题是,在其中一个方法中,该方法什么都不做,所以我可以设置一个预先完成的任务并每次都返回它。

public sealed class Test1 : Base
{
    readonly Task _emptyTask;

    public Test1()
    {
        TaskCompletionSource<Object> source = new TaskCompletionSource<object>();
        source.SetResult(null);
        _emptyTask = source.Task;
    }

    public override Task DoStuffAsync()
    {
        return _emptyTask;
    }
}

(其他选项是按需返回任务,但结果总是调用此方法)

这个类的对象经常被创建,通常是循环。看一下,将_emptyTask设置为静态字段似乎是有益的,因为对于所有方法它都是相同的Task

public sealed class Test2 : Base
{
    static readonly Task _emptyTask;

    static Test2()
    {
        TaskCompletionSource<Object> source = new TaskCompletionSource<object>();
        source.SetResult(null);
        _emptyTask = source.Task;
    }

    public override Task DoStuffAsync()
    {
        return _emptyTask;
    }
}

然后我记得静态构造函数和性能的“问题”,经过一些研究(我就是这样),我决定做一个小基准测试:

Stopwatch sw = new Stopwatch();
List<Int64> test1list = new List<Int64>(), test2list = new List<Int64>();

for (int j = 0; j < 100; j++)
{
    sw.Start();
    for (int i = 0; i < 1000000; i++)
    {
        Test1 t = new Test1();
        if (!t.DoStuffAsync().IsCompleted)
            throw new Exception();
    }
    sw.Stop();
    test1list.Add(sw.ElapsedMilliseconds);

    sw.Reset();
    sw.Start();
    for (int i = 0; i < 1000000; i++)
    {
        Test2 t = new Test2();
        if (!t.DoStuffAsync().IsCompleted)
            throw new Exception();
    }
    sw.Stop();
    test2list.Add(sw.ElapsedMilliseconds);

    sw.Reset();
    GC.Collect();
}

Console.WriteLine("Test 1: " + test1list.Average().ToString() + "ms.");
Console.WriteLine("Test 2: " + test2list.Average().ToString() + "ms.");

结果很清楚:

 Test 1: 53.07 ms. 
 Test 2: 5.03 ms. 
 end

因此,尽管有一个静态构造函数,但好处超过了问题。总是衡量。