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)
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With