Code in static constructor runs slower

I noticed that start up times would vary depending on here I placed a piece of initialization code. I thought this was really weird, so I wrote up a small benchmark, which confirmed my suspicions. It seems that code that's executed before the main method is invoked is slower than normal.

Why does Benchmark(); run at different speeds depending on if called before and after the regular code path?

Here's the benchmark code:

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

The results show that Benchmark() runs almost twice slower for both the static constructor and constructor for the static Program program property:

// 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

Doubling the number of iterations in the benchmark loop causes all times to double, suggesting that the performance penalty incurred is not constant, but a factor.

// 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

Why would this be the case? It would make sense if initialization would be just as fast if it were done where it belongs. Testing was done in .NET 4, release mode, optimizations on.

Answers


This is a very interesting issue. I spent some time experimenting with variants of your program. Here are a few observations:

  1. If you move the Benchmark() static method into a different class, the performance penalty for the static constructor disappears.

  2. If you make the Benchmark() method into an instance method, the performance penalty disappears.

  3. When I profile your fast cases (1, 2) and your slow cases (3, 4), the slow cases spent the extra time in CLR helper methods, in particular JIT_GetSharedNonGCStaticBase_Helper.

Based on this information, I can speculate on what's going on. The CLR needs to ensure that every static constructor executes at most once. A complication is that the static constructors may form a cycle (e.g., if class A contains a static field of type B and class B contains a static field of type A).

When executing inside a static constructor, the JIT compiler insert checks around some static method calls to prevent potential infinite cycles due to cyclic class dependencies. Once the static method is called from outside of a static constructor, the CLR recompiles the method to remove the checks.

This should be pretty close to what's going on.


This is a very well documented fact.

Static constructors are slow. The .net runtime is not smart enough to optimize them.

refer: Performance penalty of static constructors

explicit static constructors are expensive because they require that the runtime to insure that the value is set exactly before any member of the class is accessed. The exact cost is depends on the scenario, but it can be quite noticeable in some cases.


Need Your Help

Variant Type Structure (oaidl.h) in c++ for Mac OSX?

c++ xcode macos variant safearray

I'm trying to port my c++ dll developped in windows which make extensive use of Variant (ole/com) type Structure (see https://msdn.microsoft.com/en-us/library/windows/desktop/ms221627%28v=vs.85%29....

CefSharp custom SchemeHandler

c# cefsharp

Iam using CefSharp's SchemeHandler in order to grab resources from my C# project like .css, .js or .png files using a custom url for example custom://cefsharp/assets/css/style.css