7

我注意到启动时间会有所不同,这取决于我在这里放置了一段初始化代码。我觉得这真的很奇怪,所以我写了一个小基准,证实了我的怀疑。似乎在调用 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 中完成的,发布模式,优化。

4

2 回答 2

3

这是一个有据可查的事实。

静态构造函数很慢。.net 运行时不够聪明,无法优化它们。

参考:静态构造函数的性能损失

显式静态构造函数代价高昂,因为它们要求运行时确保在访问类的任何成员之前准确设置值。确切的成本取决于场景,但在某些情况下可能非常明显。

于 2012-07-13T04:08:29.657 回答
3

这是一个非常有趣的问题。我花了一些时间试验你的程序的变体。以下是一些观察:

  1. 如果将 Benchmark() 静态方法移到不同的类中,静态构造函数的性能损失就会消失。

  2. 如果将 Benchmark() 方法变成实例方法,性能损失就会消失。

  3. 当我分析您的快速案例 (1, 2) 和您的慢速案例 (3, 4) 时,慢速案例在 CLR 帮助器方法中花费了额外的时间,特别是 JIT_GetSharedNonGCStaticBase_Helper。

根据这些信息,我可以推测发生了什么。CLR 需要确保每个静态构造函数最多执行一次。一个复杂的问题是静态构造函数可能形成一个循环(例如,如果类 A 包含类型 B 的静态字段并且类 B 包含类型 A 的静态字段)。

在静态构造函数中执行时,JIT 编译器插入检查一些静态方法调用,以防止由于循环类依赖关系而导致的潜在无限循环。从静态构造函数外部调用静态方法后,CLR 将重新编译该方法以删除检查。

这应该非常接近正在发生的事情。

于 2012-07-15T01:11:22.443 回答