问题
We are building a tool for average case runtime analysis of Java Byte Code programs. One part of this is measuring real runtimes. So we would take an arbitrary, user provided method that may or may not have a result and may or may not have side effects (examples include Quicksort, factorial, dummy nested loops, ...) and execute it (using reflection), measuring the elapsed time. (Whether or not we benchmark properly at all is besides the point here.)
In the benchmarking code, we obviously don't do anything with the result (and some methods won't even have results). Therefore, there is no telling what the JIT may do, and we have in fact observed that it seems to optimise the whole benchmarked method call away on occasion. As the benchmarked methods are not used in isolation in reality, this renders the benchmark useless.
How can we prevent JIT from doing that? We don't want to turn it off completely because then benchmarking takes ages, and we want to benchmark "real" runtimes anyway (so we want JIT to be active inside the method).
I am aware of this question but the given scenario is too narrow; we do not know the result type (if there is one) and can therefore not use the result in some fashion the JIT does not see as useless.
回答1:
The simple solution is to write a more realistic benchmark which does something almost useful so it will not be optimised away.
There are a number of trick to confuse the JIT, but these are unlikely to help you.
Here is example of a benchmark where the method is called via reflection, MethodHandle and compiled to nothing.
import java.lang.invoke.*;
import java.lang.reflect.*;
public class Main {
public static void main(String... args) throws Throwable {
for (int j = 0; j < 5; j++) {
testViaReflection();
testViaMethodHandle();
testWithoutReflection();
}
}
private static void testViaReflection() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Method nothing = Main.class.getDeclaredMethod("nothing");
int runs = 10000000; // triggers a warmup.
long start = System.nanoTime();
Object[] args = new Object[0];
for (int i = 0; i < runs; i++)
nothing.invoke(null, args);
long time = System.nanoTime() - start;
System.out.printf("A call to %s took an average of %.1f ns using reflection%n", nothing.getName(), 1.0 * time / runs);
}
private static void testViaMethodHandle() throws Throwable {
MethodHandle nothing = MethodHandles.lookup().unreflect(Main.class.getDeclaredMethod("nothing"));
int runs = 10000000; // triggers a warmup.
long start = System.nanoTime();
for (int i = 0; i < runs; i++) {
nothing.invokeExact();
}
long time = System.nanoTime() - start;
System.out.printf("A call to %s took an average of %.1f ns using MethodHandle%n", "nothing", 1.0 * time / runs);
}
private static void testWithoutReflection() {
int runs = 10000000; // triggers a warmup.
long start = System.nanoTime();
for (int i = 0; i < runs; i++)
nothing();
long time = System.nanoTime() - start;
System.out.printf("A call to %s took an average of %.1f ns without reflection%n", "nothing", 1.0 * time / runs);
}
public static void nothing() {
// does nothing.
}
}
prints
A call to nothing took an average of 6.6 ns using reflection
A call to nothing took an average of 10.7 ns using MethodHandle
A call to nothing took an average of 0.4 ns without reflection
A call to nothing took an average of 4.5 ns using reflection
A call to nothing took an average of 9.1 ns using MethodHandle
A call to nothing took an average of 0.0 ns without reflection
A call to nothing took an average of 4.3 ns using reflection
A call to nothing took an average of 8.8 ns using MethodHandle
A call to nothing took an average of 0.0 ns without reflection
A call to nothing took an average of 5.4 ns using reflection
A call to nothing took an average of 13.2 ns using MethodHandle
A call to nothing took an average of 0.0 ns without reflection
A call to nothing took an average of 4.9 ns using reflection
A call to nothing took an average of 8.7 ns using MethodHandle
A call to nothing took an average of 0.0 ns without reflection
I had assumed MethodHandles to be faster than reflection but it doesn't appear so.
回答2:
I do not believe there is any way to selectively disable JIT optimizations, except for some of the experimental ones (like escape analysis).
You say this:
We don't want to turn it off completely because then benchmarking takes ages, and we want to benchmark "real" runtimes anyway.
But what you are trying to do is precisely that. In a real runtime, method calls will be inlined and the will be optimized away if they don't do anything. So by inhibiting these optimizations you would be getting measurements for method execution time that don't match what actually happens in a real program.
回答3:
The purpose of a benchmark is to get as close to actual performance as possible, so I don't see what you would gain here. If you suspect that the JIT will do certain things, and you wouldn't want to actually disable it in normal use, your best bet is to build the benchmark with that assumption. If there are ways you can write the benchmark that would stress it out and make it behave inefficiently under the JIT, that might be useful too since running the benchmark under a profiler would help figure out when it breaks down in its efficiency.
来源:https://stackoverflow.com/questions/12176175/can-jit-be-prevented-from-optimising-away-method-calls