我注意到启动时间会因此而有所不同我放置了一段初始化代码。我觉得这很奇怪,所以我写了一个小基准,这证实了我的怀疑。似乎在调用main方法之前执行的代码比正常情况慢。
为什么Benchmark();
以不同的速度运行,具体取决于在常规代码路径之前和之后调用?
以下是基准代码:
class Program {
static Stopwatch stopwatch = new Stopwatch();
static Program program = new Program();
static void Main() {
Console.WriteLine("main method:");
Benchmark();
Console.WriteLine();
new Program();
}
static Program() {
Console.WriteLine("static constructor:");
Benchmark();
Console.WriteLine();
}
public Program() {
Console.WriteLine("public constructor:");
Benchmark();
Console.WriteLine();
}
static void Benchmark() {
for (int t = 0; t < 5; t++) {
stopwatch.Reset();
stopwatch.Start();
for (int i = 0; i < 1000000; i++)
IsPrime(2 * i + 1);
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds + " ms");
}
}
static Boolean IsPrime(int x) {
if ((x & 1) == 0)
return x == 2;
if (x < 2)
return false;
for (int i = 3, s = (int)Math.Sqrt(x); i <= s; i += 2)
if (x % i == 0)
return false;
return true;
}
}
结果表明,对于Benchmark()
属性的静态构造函数和构造函数,static Program program
的运行速度几乎慢了两倍:
// static Program program = new Program()
public constructor:
894 ms
895 ms
887 ms
884 ms
883 ms
static constructor:
880 ms
872 ms
876 ms
876 ms
872 ms
main method:
426 ms
428 ms
426 ms
426 ms
426 ms
// new Program() in Main()
public constructor:
426 ms
427 ms
426 ms
426 ms
426 ms
基准测试循环中迭代次数加倍导致所有时间加倍,这表明所产生的性能损失不是常数,而是一个因素。
// static Program program = new Program()
public constructor:
2039 ms
2024 ms
2020 ms
2019 ms
2013 ms
static constructor:
2019 ms
2028 ms
2019 ms
2021 ms
2020 ms
main method:
1120 ms
1120 ms
1119 ms
1120 ms
1120 ms
// new Program() in Main()
public constructor:
1120 ms
1128 ms
1124 ms
1120 ms
1122 ms
为什么会出现这种情况?如果初始化在它所属的地方完成,那么这将是有意义的。测试是在.NET 4,发布模式和优化中完成的。
答案 0 :(得分:3)
这是一个记录完备的事实。
静态构造函数很慢。 .net运行时不够智能,无法优化它们。
参考:Performance penalty of static constructors
显式静态构造函数很昂贵,因为它们 要求运行时确保准确设置该值 在访问该类的任何成员之前。确切的成本是 取决于场景,但在某些情况下它可能非常明显。
答案 1 :(得分:3)
这是一个非常有趣的问题。我花了一些时间试验你的程序的变种。以下是一些观察结果:
如果将Benchmark()静态方法移动到另一个类中,静态构造函数的性能损失就会消失。
如果将Benchmark()方法转换为实例方法,性能损失就会消失。
当我分析您的快速案例(1,2)和慢速案例(3,4)时,缓慢的案例在CLR帮助器方法中花费了额外的时间,特别是JIT_GetSharedNonGCStaticBase_Helper。
根据这些信息,我可以推测出现了什么。 CLR需要确保每个静态构造函数最多执行一次。一个复杂因素是静态构造函数可能形成一个循环(例如,如果类A包含类型B的静态字段,而类B包含类型A的静态字段)。
在静态构造函数内执行时,JIT编译器插入会检查一些静态方法调用,以防止由于循环类依赖性而导致潜在的无限循环。一旦从静态构造函数外部调用静态方法,CLR就会重新编译该方法以删除检查。
这应该与正在发生的事情非常接近。