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?
7
  • 1) For example, branch prediction. 2) It checks retrospectively, and rolls back if wrong 3) No Commented Jul 29, 2019 at 11:55
  • @Michael Branch prediction is unrelated to the JVM. It happens in the underlying hardware. Commented Jul 29, 2019 at 11:56
  • (3) It'd be incredibly rude of the JVM if it inserted bugs into your application, would it not? Commented Jul 29, 2019 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. Commented Jul 29, 2019 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. Commented Jul 29, 2019 at 12:17

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Wow. Great answer!
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".

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.