Static constructor code is not JIT-optimized in a lot of cases

by Mikhail Vorontsov

I have found this article problem by an accident – I had to initialize a large in-memory static table with results of quite expensive method calls in one of my classes static constructors. Surprisingly, it took much longer than I expected…

Let’s start with a simple test code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private final static int ITERS = 100000000;  //100M
 
private static void test( final String name )
{
    final long start = System.currentTimeMillis();
    long res = start;
    for ( int i = 0; i < ITERS; ++i )
    {
        for ( int j = 0; j < 20; ++j )
            res = add2( res,  sub( add( i, j ), 1 ) );
    }
    final long time = System.currentTimeMillis() - start;
    System.out.println( name + ": Time = " + time / 1000.0 + " sec, res = " + res );
}
 
public static int add( final int a, final int b )
{
    return a + b;
}
 
public static int sub( final int a, final int b )
{
    return a - b;
}
 
public static long add2( final long a, final int b )
{
    return a + b;
}
private final static int ITERS = 100000000;  //100M

private static void test( final String name )
{
    final long start = System.currentTimeMillis();
    long res = start;
    for ( int i = 0; i < ITERS; ++i )
    {
        for ( int j = 0; j < 20; ++j )
            res = add2( res,  sub( add( i, j ), 1 ) );
    }
    final long time = System.currentTimeMillis() - start;
    System.out.println( name + ": Time = " + time / 1000.0 + " sec, res = " + res );
}

public static int add( final int a, final int b )
{
    return a + b;
}

public static int sub( final int a, final int b )
{
    return a - b;
}

public static long add2( final long a, final int b )
{
    return a + b;
}

We have 4 billion additions and 2 billion subtractions to execute. How long should it take on a modern CPU? We could expect something close to number of operations / CPU frequency (all these operations could be executed on registers only, without any memory access) - something in between 1.5 and 4 seconds.

Initially we will define these methods in the same class with static constructor and call test method in a static constructor in a loop in order to trigger JIT-compilation. Surprisingly, it took 46 seconds to run test method a single time on my Core i5-3317 (1.7Ghz). At least 10 times slower than we may expect. And it took the same time for subsequent calls (I made 30 of them). Something is very wrong, I thought... Was my code actually JIT-compiled? I've added -XX:+PrintCompilation Java option - it printed that yes, my methods were compiled:

    229    1             java.lang.String::charAt (33 bytes)
    242    2             java.lang.String::hashCode (67 bytes)
    269    3             java.lang.String::indexOf (87 bytes)
    294    4             java.lang.AbstractStringBuilder::ensureCapacityInternal (16 bytes)
    305    5             java.io.Win32FileSystem::isSlash (18 bytes)
    328    6             sun.nio.cs.UTF_8$Encoder::encode (361 bytes)
    341    7             com.mvorontsov.javaperf.ConstructorTest::add (4 bytes)
    342    8             com.mvorontsov.javaperf.ConstructorTest::sub (4 bytes)
    343    9             com.mvorontsov.javaperf.ConstructorTest::add2 (5 bytes)
    343    1 %           com.mvorontsov.javaperf.ConstructorTest::test2 @ 19 (106 bytes)
  46251    1 %           com.mvorontsov.javaperf.ConstructorTest::test2 @ -2 (106 bytes)   made not entrant
From static constructor - 0: Time = 45.903 sec, res = 100001364478531232
  46253   10             com.mvorontsov.javaperf.ConstructorTest::test2 (106 bytes)
  46269    2 %           com.mvorontsov.javaperf.ConstructorTest::test2 @ 19 (106 bytes)
From static constructor - 1: Time = 45.685 sec, res = 100001364478577137
From static constructor - 2: Time = 45.509 sec, res = 100001364478622823
    

I have observed the same behaviour on both Java 1.6.0_30 and 1.7.0_02. Ok, if we can't tune it, let's fool it. Probably, JIT doesn't expect any heavy logic in static constructors, so let's define it in another class - just create an inner static class and copy all these methods into it. After that we will call both methods in a loop in the main class static constructor.

Surprise! Same logic defined in another class is properly (quite properly to be exact) optimized even on the first call from the main class static constructor:

HelperClass: From static constructor - 0: Time = 4.224 sec, res = 100001364532400513
This: From static constructor - 0: Time = 45.981 sec, res = 100001364532404740
    

For experiment, I've tried to call both test methods from other points of the program: main method, object constructor of the main class and static constructor of another inner class, which is executed a little later. You can see results in the following listing:

HelperClass: From static constructor - 0: Time = 4.224 sec, res = 100001364532400513
This: From static constructor - 0: Time = 45.981 sec, res = 100001364532404740

This: From main method: Time = 45.966 sec, res = 100001364532450724
HelperClass: From main method: Time = 2.561 sec, res = 100001364532503631

HelperClass: From object constructor: Time = 4.38 sec, res = 100001364532496691
This: From object constructor: Time = 2.559 sec, res = 100001364532501072

HelperClass: Client class: From static constructor: Time = 2.563 sec, res = 100001364532506195
HelperClass: Client class: From object constructor: Time = 2.559 sec, res = 100001364532508758
    

Calling main class test method from the main class static constructor has somehow affected its compiled code, so that the next method call - from the main method also takes too long. We can easily prove that we should blame static constructor if we will remove test method call from the static constructor (leave only helper class test call in it):

HelperClass: From static constructor - 0: Time = 4.211 sec, res = 100001364533182549

This: From main method: Time = 4.193 sec, res = 100001364533186765
HelperClass: From main method: Time = 2.558 sec, res = 100001364533199519

HelperClass: From object constructor: Time = 4.38 sec, res = 100001364533190959
This: From object constructor: Time = 4.178 sec, res = 100001364533195340

HelperClass: ClientClass: From static constructor: Time = 2.56 sec, res = 100001364533202079
HelperClass: Client class: From object constructor: Time = 2.56 sec, res = 100001364533204639
    

Summary

If you need to execute CPU-expensive logic in your class static constructor, check if it takes excessive time to execute it. In this case try to move that logic into a separate helper class.

Full source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
public class ConstructorTest {
    final static int ITERS = 100000000;  //100M
 
    static
    {
        for ( int i = 0; i < 1; ++i )
        {
          HelperClass.test("HelperClass: From static constructor - " + i);
          test2("This: From static constructor - " + i);
        }
    }
 
    private static void test2( final String name )
    {
        final long start = System.currentTimeMillis();
        long res = start;
        for ( int i = 0; i < ITERS; ++i )
        {
            for ( int j = 0; j < 20; ++j )
                res = add2( res,  sub( add( i, j ), 1 ) );
        }
        final long time = System.currentTimeMillis() - start;
        System.out.println( name + ": Time = " + time / 1000.0 + " sec, res = " + res );
    }
 
    public static int add( final int a, final int b )
    {
        return a + b;
    }
 
    public static int sub( final int a, final int b )
    {
        return a - b;
    }
 
    public static long add2( final long a, final int b )
    {
        return a + b;
    }
 
 
    public ConstructorTest()
    {
        HelperClass.test("HelperClass: From object constructor");
        test2("This: From object constructor");
    }
 
    public static void main(String[] args) {
        test2( "This: From main method" );
        final ConstructorTest obj = new ConstructorTest();
        HelperClass.test("HelperClass: From main method");
        System.out.println( obj );
        final ClientClass client = new ClientClass();
        System.out.println( client );
    }
 
 
    private static final class ClientClass
    {
        static
        {
            HelperClass.test("HelperClass: ClientClass: From static constructor");
        }
 
        public ClientClass()
        {
            HelperClass.test("HelperClass: Client class: From object constructor");
        }
    }
 
    private static final class HelperClass
    {
        private static void test( final String name )
        {
            final long start = System.currentTimeMillis();
            long res = start;
            for ( int i = 0; i < ITERS; ++i )
            {
                for ( int j = 0; j < 20; ++j )
                    res = add2( res,  sub( add( i, j ), 1 ) );
            }
            final long time = System.currentTimeMillis() - start;
            System.out.println( name + ": Time = " + time / 1000.0 + " sec, res = " + res );
        }
 
        public static int add( final int a, final int b )
        {
            return a + b;
        }
 
        public static int sub( final int a, final int b )
        {
            return a - b;
        }
 
        public static long add2( final long a, final int b )
        {
            return a + b;
        }
    }
}
public class ConstructorTest {
    final static int ITERS = 100000000;  //100M

    static
    {
        for ( int i = 0; i < 1; ++i )
        {
          HelperClass.test("HelperClass: From static constructor - " + i);
          test2("This: From static constructor - " + i);
        }
    }

    private static void test2( final String name )
    {
        final long start = System.currentTimeMillis();
        long res = start;
        for ( int i = 0; i < ITERS; ++i )
        {
            for ( int j = 0; j < 20; ++j )
                res = add2( res,  sub( add( i, j ), 1 ) );
        }
        final long time = System.currentTimeMillis() - start;
        System.out.println( name + ": Time = " + time / 1000.0 + " sec, res = " + res );
    }

    public static int add( final int a, final int b )
    {
        return a + b;
    }

    public static int sub( final int a, final int b )
    {
        return a - b;
    }

    public static long add2( final long a, final int b )
    {
        return a + b;
    }


    public ConstructorTest()
    {
        HelperClass.test("HelperClass: From object constructor");
        test2("This: From object constructor");
    }

    public static void main(String[] args) {
        test2( "This: From main method" );
        final ConstructorTest obj = new ConstructorTest();
        HelperClass.test("HelperClass: From main method");
        System.out.println( obj );
        final ClientClass client = new ClientClass();
        System.out.println( client );
    }


    private static final class ClientClass
    {
        static
        {
            HelperClass.test("HelperClass: ClientClass: From static constructor");
        }

        public ClientClass()
        {
            HelperClass.test("HelperClass: Client class: From object constructor");
        }
    }

    private static final class HelperClass
    {
        private static void test( final String name )
        {
            final long start = System.currentTimeMillis();
            long res = start;
            for ( int i = 0; i < ITERS; ++i )
            {
                for ( int j = 0; j < 20; ++j )
                    res = add2( res,  sub( add( i, j ), 1 ) );
            }
            final long time = System.currentTimeMillis() - start;
            System.out.println( name + ": Time = " + time / 1000.0 + " sec, res = " + res );
        }

        public static int add( final int a, final int b )
        {
            return a + b;
        }

        public static int sub( final int a, final int b )
        {
            return a - b;
        }

        public static long add2( final long a, final int b )
        {
            return a + b;
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *