3

This question is mostly related to the way language implementer do implements static class initalization (pretty specialized question). It is for curiosity and also to potentially improve my code to gain few nanoseconds

For most object oriented language I know (C++, C#, Java), there is a possibility to define a static constructor and/or static member definition with initialization values.

A language definition could initialize class level static parts:

  • First use of a dll or exe (module load) and in module order of compilation (I think C++)
  • First usage of the class. (I think C# and Java)
  • Potentially other obscurs ones ???

I think that experience showed that it is a lot preferable to initialize static parts on first usage of the object in order to ensure proper precedence of dependencies between classes.

But using static parts initialization on first usage of a class would appears to me to add a hit in performance to any class. That is because on: each call to a constructor, or each call to a static member or functions, the language will have to check if the static initializer has been run prior to give access to them.

So, is there a way (a twist), a language implementation could do to prevent this performance hit, and if yes how?

In the same time... Depending on implementation, can we get class performance improvement by not using any static parts (member, constructor) for that class? I mean compiler will know that for that class, runtime does not have to check if static initalizer has already been call.

Just to be more clear, to ask in another way:

The problem with static initalization is because it need to be called only once for the lifetime of an application and only once. In modern language (C# and Java), it is called on first usage of that class (any usage, static member usage or instance constructor) could trigger the static initialization. But it has to be done only once. So does the compiler check that on each and every static access to that class and/or to any constructor call? Or does the compiler does a magic trick to prevent that (check on each and every call to static member and/or constructor call)?

Eric Ouellet
  • 151
  • 8
  • Does this answer your question? [Is micro-optimisation important when coding?](https://softwareengineering.stackexchange.com/questions/99445/is-micro-optimisation-important-when-coding) – gnat Jul 01 '21 at 14:32
  • There is a relation but my question is more specific. I would really like to know if there is a twist of not for static initialization (performance issue) and/or if not using static stuff could give us better performance. – Eric Ouellet Jul 01 '21 at 14:36
  • Ultimately, this will depend on the specific implementation of the language (and the version of the implementation), not on the language itself. – Doc Brown Jul 01 '21 at 15:32
  • 5
    "would appear" and "performance hit" do not go together. Humans have repeatedly proved to be **incapable** of predicting the effect of programming language constructs on performance. You **must** measure under realistic conditions to answer this kind of question. – Kilian Foth Jul 01 '21 at 16:14
  • 2
    There are a variety of techniques that can be used to reduce or eliminate any hit after the statics have been initialized, particularly in a JIT compiled language. This includes updating function pointers (from a call to handle the statics to one that skips it) or (re)generating code to bypass the code that does the static initialization/construction. – 1201ProgramAlarm Jul 01 '21 at 16:28
  • Whenever you use an instance method, the runtime has to check to ensure that the object reference is not null. When you use a static method, no such check is required. My guess is that checking an instance for null and checking a type for initialization will have a similar (very small) performance expense. – John Wu Jul 02 '21 at 23:12

3 Answers3

2

That is because on: each call to a constructor, or each call to a static member or functions, the language will have to check if the static initializer has been run prior to give access to them.

I'm going to only talk about Java here because I am confident in my understanding of how this works and I know where the documentation is for that. I expect C# and C++ might have different details but the same top-level answer for your question.

Class initialization is described in the JLS section 12.4. I'm looking at the version 11 spec.

First, it discusses when initialization occurs which is immediately before the first time any of the following happens:

  • an instance of the class is created i.e. one of its constructors is called.
  • A static method of the class is called.
  • A static field of the class is assigned.
  • A non-constant static field of the class is referenced.

Next it describes how the initialization status is tracked. The Class object the represents the class contains state that indicates one of 4 situations:

  1. not intialized
  2. currently being initialized
  3. fully initialized
  4. initialization was attempted but failed

We can ignore states 2 and 4 for this answer. We'll just consider and just think of this state as saying whether the class is initialized. This state can be used to determine whether a class needs to be loaded.

For example, before constructing an object or a given class, there will be a check to determine if the class has been initialized. There could be optimizations around this state checking; but they must follow the same semantics. The important thing to understand is that the class will be initialized once and only once.‡

When this first access occurs, the JLS describes the procedure for initializing the class. Much of this is related to thread safety, assertions, etc. which I am going to ignore for simplicity:

  1. Initialize and parent classes or interfaces that have not been initialized.
  2. execute the class variable initializers and static initializers of the class in textual order, "as though they were a single block".
  3. Set the state on the Class object as initialized

Note that in step 2, it's not required that you have a static block. If you have any static variables, they will be initialized at this time either to an assignment in the definition or their default e.g.: null.

The upshot here is that the class initialization executes when the class is initialized only once and every class access (other than constants) requires that the class be initialized. It makes no difference whether you have a static initializer or even if you have static variables. Every class requires initialization.

‡ I'm assuming a single classloader here. If you have multiple classloaders a class may be loaded more than once but this is outside the scope of this answer.

JimmyJames
  • 24,682
  • 2
  • 50
  • 92
  • Not sure to understand your answer. The problem with static initalization is because it need to be called only once for the lifetime of an application and only once. In modern language (C# and Java), it is called on fist usage of that class (any usage, static member usage or instance constructor) could trigger the static initialization. But it has to be done only once. So does the compiler check that on each and every static access to that class and/or to any constructor call? Or does the compiler does a magic trick to prevent that check on each and every call static member and constructor? – Eric Ouellet Jul 02 '21 at 00:49
  • I think my question was not clear enough. I added more information in an attempt to be a little be clearer (english is not my first language). – Eric Ouellet Jul 02 '21 at 00:54
  • @EricOuellet Your edits seem to confirm my understanding of the question. My answer's wording can be improved. I will update and provide some references. – JimmyJames Jul 02 '21 at 16:11
  • @EricOuellet I've updated my answer. Let me know if it is still unclear. – JimmyJames Jul 02 '21 at 17:28
  • at 12.4 : "Initialization of a class consists of executing its static initializers and the initializers for static fields (class variables) declared in the class." ... So when you say: "It makes no difference whether you have a static initializer or even if you have static variables." I wonder if you are sure about that. I'm actually running a test in C# that I've done an hour ago (not very scientific due to thread switching time) but according to actual results, I would say that, at least for C#, it looklikes to make a real diff when there is static stuffs declared or not. – Eric Ouellet Jul 02 '21 at 18:12
  • The document you reference is pretty nice. It is pretty clear. But it does not says if the check is always done either when there is no static declared stuff, which is the main purpose of my question. I'm doing not very scientific tests right now and should get interesting results which should show, I think, that having no static should add an extremely little gain in performance. But my test should take 2-3 hours in order to complete (long tests should reduce the thread switching effect and makes results more accurate). – Eric Ouellet Jul 02 '21 at 18:16
  • "it looklikes to make a real diff when there is static stuffs declared or not." During initialization or on every access to the class? – JimmyJames Jul 02 '21 at 18:19
  • I do not have enough room here to show my result and honestly my actual test does not worth a penny. I have better performance for a class without static stuff calling an instance method but testing with an instance method is fucking stupid and prove nothing. So I have to change my test using a static method. I will keep you informed of my results. I'm measuring time to do millions calls in order to see if a diff exists between the 2 (with and without static initalizer). And I run that test 100 times to lower the thread swithing time effect (reduce the error). – Eric Ouellet Jul 02 '21 at 18:26
  • "But it does not says if the check is always done either when there is no static declared stuff" But it does: "A class or interface type T **will be initialized** immediately before the first occurrence of any one of the following: ..." There are no qualifications in that statement. – JimmyJames Jul 02 '21 at 18:26
  • I agree my tests are not actually a proof of anything. But having consistant results should show with high confidence if there is a difference between using static stuff or not. By the way, I upvote your question. I will probably vote it as the best answer but I always wait few days before doing so. – Eric Ouellet Jul 02 '21 at 18:30
  • @EricOuellet A few things. C# could be different; I'm just assuming it is similar to Java in this regard and what kind of time difference are you seeing? It's highly unlikely you could see a measurable difference without running millions of iterations unless you are running on antiquated hardware. One other problem is that compilers and JIT can really make it hard to get accurate micro-benchmarks. Small things like the order in which the methods are called can change your micro-performance statistics. – JimmyJames Jul 02 '21 at 18:35
  • I agree. I loop 32 bits time (4 000 000 000+) which should reduce any potential error and give pretty accurate average results. The potential errors are removed by huge amount of tests. The only real problem is the garbage collector but there should be none because I do not allocate memory. I will probably do another big tests after about instantiation that will requires me to be more careful and clear to GB before each test. Just for the info, I have partial results and it sounds like a very very little improvement but only when no static at all are used. – Eric Ouellet Jul 02 '21 at 18:51
  • Partial results seems to tell that a static function (although it should have no effect because no init is required) does incur the same delay as a static constructor or static variable declaration... At least in C# in my actual environment which could be anything else on other version. But, it tend to show bad optimization on the compiler which only check for static word presence. – Eric Ouellet Jul 02 '21 at 18:55
  • The JIT is quite significant here, because the fact that initialization is at most once means that once the initialization has happened, the JIT can assume it will remain having happened. So once methods in the class are JITed, the checks for initialization don't need need to be present in the direct calls to JITed code. – user1937198 Jul 03 '21 at 10:18
2

It depends on how the language can leverage it's execution environment. There are 4 main classes of static construction: constant/compile time evaluation, assembly level loading, class based lazy loading, and first use based loading. Each has different requirements and costs.

Constant/compile time evaluation

From a runtime perspective, this is the simplest option, the compiler prepares a constant block of bytes as part of the compilation process, and the loader just has to copy it into memory, either as part of assembly level load, or lazily using OS provided memory access primitives. What you can initialize is limited by the language and the need to construct the values ahead of time.

Assembly level loading

This is used by non-local statics in C++. The code is run by the loader at the point the assembly is loaded into the process. This causes some issues, as the initialization code is run in an environment where it is undefined as to whether other statics have been initialized yet. However, once loading is complete all other code does not need to check for initialization, as by definition if loading is complete, initialization is complete.

Class based lazy loading

This is used by the JVM and CLR. The initialisation is called at the first use of the class. This prevents ordering issues, as so long as there is not a cycle, if a constructor depends on a field that itself has static initialization, the runtime will pause the first constructor on its first use of the field, to run the dependent initialization code. The downside is that implemented naively, this would require lots of checks. However the major implementations avoid that by using JIT compilation to avoid that cost. When the calling code is first generated, a thunk is generated which will start loading the clas when called. Once loading and initialization is complete, that thunk is replaced with a call to the actual class, which is guaranteed to have already been initialized, so the class code does not worry about initialization. *

First use based lazy loading

This is used by local statics in C++ and the lazy_static package in rust. In this case, a check is inserted on every use to check if the initialisation has happened, which does have a small runtime cost.

* I am for the purpose of simplification ignoring the complex process where a function may be executed by calling into the interpreter, rather than generated code. The point is in the process of loading class code into an executable state, the runtime initialization has happened, and replaces code that triggers loading/initialisation.

user1937198
  • 437
  • 3
  • 7
0

I post this code as reference for C#.

In Debug mode, some compilation optimization are not there and the last column is a bit faster but I don't understand why.

In release, the first column is slower which sounds like their is no usage of thunk on first call (optimization like mentionned in the acccepted answer). It sounds like there is always a constant check, I mean there is always a hit in performance when using any kind of static initialization.

I you ever know, please let met know.

Results in Debug mode:

Average With= 25605, Without=25588, WithoutInline=25597, Plain=24576

Results in Release mode:

Average With= 6395, Without=5329, WithoutInline=5328, Plain=5328

Code:

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Windows;

namespace StaticInitEffect
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Test();
        }

        public class ClassWithStaticInit
        {
            public static void Test() { MessageBox.Show("Test"); } // To prevent compiler from optimise because it is the first method, if ever possible???

            public static int IncreaseVal(int val)
            {
                return ++val;
            }

            public static int Val = 0;

            static ClassWithStaticInit()
            {
                Val = 1;
            }
        }

        public class ClassWithoutStaticInit
        {
            public static void Test() { MessageBox.Show("Test"); } // To prevent compiler from optimise because it is the first method, if ever possible???

            public static int IncreaseVal(int val)
            {
                return ++val;
            }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public static int IncreaseVal2(int val)
            {
                return ++val;
            }

        }

        public class ClassPlain
        {
            public int IncreaseVal(int val)
            {
                return ++val;
            }
        }


        private void Test()
        {
            int val;
            const int max = int.MaxValue;

            ClassWithStaticInit with = new ClassWithStaticInit();
            ClassWithoutStaticInit without = new ClassWithoutStaticInit();
            ClassPlain plain = new ClassPlain();

            long millisecWith = 0;
            long millisecWithout = 0;
            long millisecWithoutInline = 0;
            long millisecPlain = 0;

            for (int n = 0; n < 100; n++)
            {
                Stopwatch sw1 = new Stopwatch();
                sw1.Start();
                val = int.MinValue;
                while (val < max)
                {
                    val = ClassWithStaticInit.IncreaseVal(val);
                }
                sw1.Stop();

                Stopwatch sw2 = new Stopwatch();
                sw2.Start();
                val = int.MinValue;
                while (val < max)
                {
                    val = ClassWithoutStaticInit.IncreaseVal(val);
                }
                sw2.Stop();

                Stopwatch sw3 = new Stopwatch();
                sw3.Start();
                val = int.MinValue;
                while (val < max)
                {
                    val = ClassWithoutStaticInit.IncreaseVal(val);
                }
                sw3.Stop();

                Stopwatch sw4 = new Stopwatch();
                sw4.Start();
                val = int.MinValue;
                while (val < max)
                {
                    val = plain.IncreaseVal(val);
                }
                sw4.Stop();

                Debug.WriteLine($"With= {sw1.ElapsedMilliseconds}, Without={sw2.ElapsedMilliseconds}, WithoutInline={sw3.ElapsedMilliseconds}, Plain={sw4.ElapsedMilliseconds}");
                millisecWith += sw1.ElapsedMilliseconds;
                millisecWithout += sw2.ElapsedMilliseconds;
                millisecWithoutInline += sw3.ElapsedMilliseconds;
                millisecPlain += sw4.ElapsedMilliseconds;
            }

            Debug.WriteLine("Average");
            Debug.WriteLine($"With= {millisecWith / 100}, Without={millisecWithout / 100}, WithoutInline={millisecWithoutInline / 100}, Plain={millisecPlain / 100}");
        }
    }
}
Eric Ouellet
  • 151
  • 8