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

时间:2021-06-30 10:56:30

Recently read in a article on dotnetpearls.com here saying that static ctors take a substantial amount of perfomance hit.

最近在dotnetpearls.com上的一篇文章中读到这里的说法,静态ctors采取了大量的性能。

Could not fathom why?

无法理解为什么?

4 个解决方案

#1


18  

I think "substantial" is an overstatement in most use cases.

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

Having a static constructor (even if it does nothing) affects type initialization time due to the presence/absence of the beforefieldinit flag. There are stricter guarantees about timing when you have a static constructor.

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

For most code, I'd suggest this doesn't make much difference - but if you're tight-looping and accessing a static member of a class, it might. Personally I wouldn't worry about it too much - if you have a suspicion that it's relevant in your real application, then test it rather than guessing. Microbenchmarks are very likely to exaggerate the effect here.

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

It's worth noting that .NET 4 behaves somewhat differently to previous versions when it comes to type initialization - so any benchmarks should really show the different versions in order to be relevant.

值得注意的是,当涉及到类型初始化时,.NET 4的行为与以前的版本有些不同 - 所以任何基准测试都应该真正显示不同的版本以便相关。

#2


11  

Well I've just replicated his test.

好吧,我刚刚复制了他的测试。

For 1000000000 iterations with a DEBUG build I get:

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

  • 4s for his static class with a static constructor
  • 4s为他的静态类与静态构造函数

  • 3.6s same class with commented out static constructor
  • 3.6s相同的类,带有注释掉的静态构造函数

  • 2.9s with the class non-static (and creating an instance before the iteration) with either a static constructor or not
  • 2.9s与非静态类(并在迭代之前创建一个实例)与静态构造函数或不

The same with a RELEASE build does highlight a difference:

与RELEASE构建相同的功能确实突出了一个区别:

  • Static class with static constructor: 4046.875ms
  • 带静态构造函数的静态类:4046.875ms

  • Static class with no static constructor: 484.375ms
  • 没有静态构造函数的静态类:484.375ms

  • Instance with static constructor: 484.375ms
  • 具有静态构造函数的实例:484.375ms

  • Instance with no static constructor: 484.375ms
  • 没有静态构造函数的实例:484.375ms

#3


5  

The CLR provides a pretty strong guarantee for the execution of static constructors, it promises to call them only once and before any method in the class can run. That guarantee is fairly tricky to implement when there are multiple threads using the class.

CLR为静态构造函数的执行提供了非常强大的保证,它承诺只调用它们一次,然后才能运行类中的任何方法。当有多个线程使用该类时,实现这种保证是相当棘手的。

Taking a peek at the CLR source code for SSCLI20, I see a fairly large chunk of code dedicated to providing this guarantee. It maintains a list of running static constructors, protected by a global lock. Once it gets an entry in that list, it switches to a class specific lock which ensures no two threads can be running the constructor. Double-checked locking on a status bit that indicates that the constructor was already run. Lots of inscrutable code that provides exception guarantees.

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

Well, this code doesn't come for free. Add it to the execution time for the cctor itself and you're looking at some overhead. As always, don't let this cramp your style, this guarantee is also a very nice one that you wouldn't want to provide yourself. And measure before you fix.

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

#4


0  

I just did a small test to check the impact of adding a static constructor to one of my classes.

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

I have a base class that looks like this:

我有一个基类,看起来像这样:

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

The problem is, that in one of the implementations that method does nothing, so I can set a pre-made completed task and return it every time.

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

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;
    }
}

(Other option is to return the task on demand, but turns out this method is always called)

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

Objects of this class are created very very often, usually in loops. Looking at it, it looks like setting _emptyTask as a static field would be beneficial since it would be the same Task for all methods:

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

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;
    }
}

Then I remember the "issue" with static constructors and performance, and after research a little (that is how I get here), I decide to do a small benchmark:

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

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.");

And the results are quite clear:

结果很清楚:

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

So despite of having a static constructor, the benefit outweighs the issue. So always measure.

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

#1


18  

I think "substantial" is an overstatement in most use cases.

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

Having a static constructor (even if it does nothing) affects type initialization time due to the presence/absence of the beforefieldinit flag. There are stricter guarantees about timing when you have a static constructor.

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

For most code, I'd suggest this doesn't make much difference - but if you're tight-looping and accessing a static member of a class, it might. Personally I wouldn't worry about it too much - if you have a suspicion that it's relevant in your real application, then test it rather than guessing. Microbenchmarks are very likely to exaggerate the effect here.

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

It's worth noting that .NET 4 behaves somewhat differently to previous versions when it comes to type initialization - so any benchmarks should really show the different versions in order to be relevant.

值得注意的是,当涉及到类型初始化时,.NET 4的行为与以前的版本有些不同 - 所以任何基准测试都应该真正显示不同的版本以便相关。

#2


11  

Well I've just replicated his test.

好吧,我刚刚复制了他的测试。

For 1000000000 iterations with a DEBUG build I get:

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

  • 4s for his static class with a static constructor
  • 4s为他的静态类与静态构造函数

  • 3.6s same class with commented out static constructor
  • 3.6s相同的类,带有注释掉的静态构造函数

  • 2.9s with the class non-static (and creating an instance before the iteration) with either a static constructor or not
  • 2.9s与非静态类(并在迭代之前创建一个实例)与静态构造函数或不

The same with a RELEASE build does highlight a difference:

与RELEASE构建相同的功能确实突出了一个区别:

  • Static class with static constructor: 4046.875ms
  • 带静态构造函数的静态类:4046.875ms

  • Static class with no static constructor: 484.375ms
  • 没有静态构造函数的静态类:484.375ms

  • Instance with static constructor: 484.375ms
  • 具有静态构造函数的实例:484.375ms

  • Instance with no static constructor: 484.375ms
  • 没有静态构造函数的实例:484.375ms

#3


5  

The CLR provides a pretty strong guarantee for the execution of static constructors, it promises to call them only once and before any method in the class can run. That guarantee is fairly tricky to implement when there are multiple threads using the class.

CLR为静态构造函数的执行提供了非常强大的保证,它承诺只调用它们一次,然后才能运行类中的任何方法。当有多个线程使用该类时,实现这种保证是相当棘手的。

Taking a peek at the CLR source code for SSCLI20, I see a fairly large chunk of code dedicated to providing this guarantee. It maintains a list of running static constructors, protected by a global lock. Once it gets an entry in that list, it switches to a class specific lock which ensures no two threads can be running the constructor. Double-checked locking on a status bit that indicates that the constructor was already run. Lots of inscrutable code that provides exception guarantees.

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

Well, this code doesn't come for free. Add it to the execution time for the cctor itself and you're looking at some overhead. As always, don't let this cramp your style, this guarantee is also a very nice one that you wouldn't want to provide yourself. And measure before you fix.

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

#4


0  

I just did a small test to check the impact of adding a static constructor to one of my classes.

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

I have a base class that looks like this:

我有一个基类,看起来像这样:

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

The problem is, that in one of the implementations that method does nothing, so I can set a pre-made completed task and return it every time.

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

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;
    }
}

(Other option is to return the task on demand, but turns out this method is always called)

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

Objects of this class are created very very often, usually in loops. Looking at it, it looks like setting _emptyTask as a static field would be beneficial since it would be the same Task for all methods:

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

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;
    }
}

Then I remember the "issue" with static constructors and performance, and after research a little (that is how I get here), I decide to do a small benchmark:

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

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.");

And the results are quite clear:

结果很清楚:

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

So despite of having a static constructor, the benefit outweighs the issue. So always measure.

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