我最近在dotnetpearls.com上读了一篇文章,在这里说静态构造函数会导致性能受到相当大的影响。
不明白为什么会这样?
我认为"substantial"在大多数情况下都有些夸张。
即使静态构造函数什么也不做,由于beforefieldinit标志的存在/缺失,它也会影响类型初始化时间。当你拥有一个静态构造函数时,时间保证更加严格。
对于大多数代码,我建议这并没有太大区别 - 但如果你正在紧密循环和访问类的静态成员,那就可能会有所不同。个人认为不必太过担心 - 如果你怀疑它在你的真实应用程序中有关,那么测试它而不是猜测。在这里,微基准测试很可能会夸大效果。
值得注意的是,.NET 4 在类型初始化方面与之前的版本有所不同 - 因此,任何基准测试应该真正显示不同的版本才能具有相关性。
我刚刚复制了他的测试。
在使用DEBUG版本进行1000000000次迭代后,我得到如下结果:
同样是使用RELEASE版本,则会出现差异:
我刚刚进行了一个小测试,以检查向我的某个类添加静态构造函数的影响。
我有一个基类,看起来像这样:
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
因此,尽管存在静态构造函数,但好处大于问题。因此,请始终进行测量。