Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Static constructor performance and why we can't specify beforefieldinit

I came across a difference in speed using the following two structs:

public struct NoStaticCtor
{
    private static int _myValue = 3;
    public static int GetMyValue() { return _myValue; }
}

public struct StaticCtor
{
    private static int _myValue;
    public static int GetMyValue() { return _myValue; }
    static StaticCtor()
    {
        _myValue = 3;
    }
}

class Program
{
    static void Main(string[] args)
    {
        long numTimes = 5000000000; // yup, 5 billion
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (long i = 0; i < numTimes; i++)
        {
            NoStaticCtor.GetMyValue();
        }
        sw.Stop();
        Console.WriteLine("No static ctor: {0}", sw.Elapsed);

        sw.Restart();
        for (long i = 0; i < numTimes; i++)
        {
            StaticCtor.GetMyValue();
        }
        sw.Stop();
        Console.WriteLine("with static ctor: {0}", sw.Elapsed);
    }
}

Which produces the results:

Release (x86), no debugger attached:
No static ctor: 00:00:05.1111786
with static ctor: 00:00:09.9502592

Release (x64), no debugger attached:
No static ctor: 00:00:03.2595979
with static ctor: 00:00:14.5922220

The compiler produces a static constructor for the NoStaticCtor that is identical to the one explicitly declared in StaticCtor. I understand that the compiler will only emit beforefieldinit when a static constructor is not explicitly defined.

They produce almost identical il code, except for one difference, declaring the struct with beforefieldinit, which is where I feel the difference lies since I know it determines when the type constructor is called, though I can't quite figure out why there's such a difference. It assume it isn't calling the type constructor every iteration, since a type constructor can only be called once.1

So,

1) Why the time difference between the struct with beforefieldinit and the one without? (I imagine the JITer is doing something extra in the for loop, however, I have no clue how to view the output of the JITer to see what.

2) Why did the compiler designers a) not make all structs beforefieldinit be default and b) not give developers the ability to explicitly specify that behavior? Of course, this is assuming you can't, as I haven't been able to find a way.


Edit:

1. I modified the code, essentially running each loop a second time, expecting a improvement, but it wasn't much:

No static ctor: 00:00:03.3342359
with static ctor: 00:00:14.6139917
No static ctor: 00:00:03.2229995
with static ctor: 00:00:12.9524860
Press any key to continue . . .

I did this because I though, well, maybe, however unlikely it is, the JITer was actually calling the type constructor every iteration. It seems to me the JITer would know the type constructor has already been called and not emit code to do that when the second loop was compiled.

In addition to Motti's answer: This code produces better results, because of difference in JITing, the JITing of DoSecondLoop doesn't emit the static ctor check, because it detected it was done previously in DoFirstLoop, causing each loop to perform at the same speed. (~3 seconds)

like image 749
Christopher Currens Avatar asked Feb 02 '23 11:02

Christopher Currens


1 Answers

The first time your type is accessed, the static ctor (whether generated explicitly or implicitly) has to be executed.

When the JIT compiler compiles IL to native instructions it checks whether the static ctor for that type has been executed and if not emits native code that checks (again) whether the static ctor has been executed and if not, executes it.

The jitted code is cached for future calls of the same method (and this is why the code had to check again if the static ctor was executed).

The thing is, if the jitted code that checks for the static ctor is in a loop, this test will occur in every iteration.

When beforefieldinit is present, the JIT compiler is allowed to optimize the code, by testing for the static ctor invocation BEFORE entering the loop. If it's not present, this optimization is not allowed.

The C# compiler automatically decides for us when to emit this attribute. It currently (C# 4) emits it only if the code does NOT explicitly defines a static constructor. The idea behind it is if you defined a static ctor yourself, the timing might be more important to you and the ststic ctor should not execute ahead of time. This may or may not be true, but you can't change this behavior.

Here is a link to the part of my online .NET tutorial that explain this in detail: http://motti.me/c1L

BTW, I don't recommend using static ctors on structs because believe it or not, they are not guaranteed to execute! This wasn't part of the question, so I won't elaborate, but see this for more detail if your'e interested: http://motti.me/c1I (I touch the subject at about 2:30 into the video).

I hope this helps!

like image 73
Motti Shaked Avatar answered Feb 06 '23 11:02

Motti Shaked