1

In section 12.3.3., "Unrealistic Sampling of Code Paths" the Java Concurrency In Practice book says:

In some cases, the JVM may make optimizations based on assumptions that may only be true temporarily, and later back them out by invalidating the compiled code if they become untrue

I cannot understand above statement.

  1. What are these JVM assumptions?
  2. How does the JVM know whether the assumptions are true or untrue?
  3. If the assumptions are untrue, does it influence the correctnes of my data?
Marco13
  • 53,703
  • 9
  • 80
  • 159
  • 1) For example, [branch prediction](https://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-processing-an-unsorted-array). 2) It checks retrospectively, and rolls back if wrong 3) No – Michael Jul 29 '19 at 11:55
  • @Michael Branch prediction is unrelated to the JVM. It happens in the underlying hardware. – Marco13 Jul 29 '19 at 11:56
  • (3) It'd be incredibly rude of the JVM if it inserted bugs into your application, would it not? – Slaw Jul 29 '19 at 11:57
  • @Marco13 The JVM most certainly does observe branching behaviour and optimises accordingly. Calling that 'branch prediction' was possibly incorrect of me - I'm seems now I've searched for it that that term is usually only applied to CPU behaviour - but the principle is the same. – Michael Jul 29 '19 at 12:03
  • @Michael There is a lot of black magic going on in the different tiers of JIT compilation. Some decisions are based on profiling information, but I don't know *which* information is used there, and *how*. I have never heard of optimizations that could be called "JVM-level-branch prediction", but it *might* be the case that the JVM does this at some point. – Marco13 Jul 29 '19 at 12:17
  • @Marco13 Not exactly comprehensive, but [the following talk](https://vimeo.com/69631795) (~14:20) from Vladamir Ivanov lists branch prediction as an optimization. There is also a slide titled 'Optimizations' a few minutes later which lists *untaken branch pruning* and *branch frequency prediction* as speculative techniques which are used, though he does not go into specifics. – Michael Jul 29 '19 at 12:20
  • @Michael I have added a link to https://wiki.openjdk.java.net/display/HotSpot/PerformanceTacticIndex#PerformanceTacticIndex-speculative(profile-based)techniques which also mentions "branch frequency prediction" - although one would have to dig a lot deeper in order to understand *how* this is used exactly. – Marco13 Jul 29 '19 at 12:37

2 Answers2

6

The statement that you quoted has a footnote which gives an example:

For example, the JVM can use monomorphic call transformation to convert a virtual method call to a direct method call if no classes currently loaded override that method, but it invalidates the compiled code if a class is subsequently loaded that overrides the method.

The details are very, very, very complex here. So the following is a extremely oversimpilified example.

Imagine you have an interface:

 interface Adder { int add(int x); }

The method is supposed to add a value to x, and return the result. Now imagine that there is a program that uses an implementation of this class:

class OneAdder implements Adder { 
    int add(int x) {
        return x+1;
    }
}

class Example {

    void run() {
        OneAdder a1 = new OneAdder();
        int result = compute(a1);
        System.out.println(result);
    }

    private int compute(Adder a) {
        int sum = 0;
        for (int i=0; i<100; i++) {
            sum = a.add(sum);
        }
        return sum;
    }
}

In this example, the JVM could do certain optimizations. A very low-level one is that it could avoid using a vtable for calling the add method, because there is only one implementation of this method in the given program. But it could even go further, and inline this only method, so that the compute method essentially becomes this:

private int compute(Adder a) {
    int sum = 0;
    for (int i=0; i<100; i++) {
        sum += 1;
    }
    return sum;
}

and in principle, even this

private int compute(Adder a) {
    return 100;
}

But the JVM can also load classes at runtime. So there may be a case where this optimization has already been done, and later, the JVM loads a class like this:

class TwoAdder implements Adder { 
    int add(int x) {
        return x+2;
    }
}

Now, the optimization that has been done to the compute method may become "invalid", because it's not clear whether it is called with a OneAdder or a TwoAdder. In this case, the optimization has to be undone.

This should answer 1. of your question.

Regarding 2.: The JVM keeps track of all the optimizations that have been done, of course. It knows that it has inlined the add method based on the assumption that there is only one implementation of this method. When it finds another implementation of this method, it has to undo the optimization.

Regarding 3.: The optimizations are done when the assumptions are true. When they become untrue, the optimization is undone. So this does not affect the correctness of your program.


Update:

Again, the example above was very simplified, referring to the footnote that was given in the book. For further information about the optimization techniques of the JVM, you may refer to https://wiki.openjdk.java.net/display/HotSpot/PerformanceTechniques . Specifically, the speculative (profile-based) techniques can probably be considered to be mostly based on "assumptions" - namely, on assumptions that are made based on the profiling data that has been collected so far.

Marco13
  • 53,703
  • 9
  • 80
  • 159
1

Taking the quoted text in context, this section of the book is actually talking about the importance of using realistic text data (inputs) when you do performance testing.

Your questions:


What are these JVM assumptions?

I think the text is talking about two things:

  • On the one hand, it seems to be talking about optimizing based on the measurement of code paths. For example whether the "then" or "else" branch of an if statement is more likely to be executed. This can indeed result in generation of different code and is susceptible to producing sub-optimal code if the initial measurements are incorrect.

  • On the other hand, it also seems to be talking about optimizations that may turn out to be invalid. For example, at a certain point in time, there may be only one implementation of a given interface method that has been loaded by the JVM. On seeing this, the optimizer may decide to simplify the calling sequence to avoid polymorphic method dispatching. (The term used in the book for this a "monomorphic call transformation".) A bit latter, a second implementation may be loaded, causing the optimizer to back out that optimization.

The first of these cases only affects performance.

The second of these would affect correctness (as well as performance) if the optimizer didn't back out the optimization. But the optimizer does do that. So it only affects performance. (The methods containing the affected calls need to be re-optimized, and that affects overall performance.)

How do JVM know the assumptions are true or untrue?

In the first case, it doesn't.

In the second case, the problem is noticed when the JVM loads the 2nd method, and sees a flag on (say) the interface method that says that the optimizer has assumed that it is effectively a final method. On seeing this, the loader triggers the "back out" before any damage is done.

If the assumptions are untrue, does it influence the correctness of my data?

No it doesn't. Not in either case.


But the takeaway from the section is that the nature of your test data can influence performance measurements. And it is not simply a matter of size. The test data also needs to cause the application to behave the same way (take similar code paths) as it would behave in "real life".

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216