Not so much a question but an observation.
I'm somewhat surprised that the JIT compiler doesn't inline the use of the Optional class since it seems to be a heavily used part of the language since Java 8. I was expecting the following two test methods to perform equivalent:
import static java.util.Optional.*;
public static class TestClass {
int i;
public void test1(Integer x) {
i = ofNullable(x).orElse(-1);
}
public void test2(Integer x) {
if (x == null)
i = -1;
else
i = x;
}
}
Instead, test1 always allocates an Optional for non-null values and is therefore 20x slower than test2. It just seems like this code should be easily optimized by the JIT compiler.
I tested this on Java 8 and Java 11. Anyone know if newer versions of Java do a better job at optimizing this? In general I like the terseness of using ofNullable over if/else statements, but I can't use them in critical code paths due to heavy GC.
Edit: Here's the benchmarking code I used:
@BenchmarkOptions(benchmarkRounds = 100, warmupRounds = 20)
public class BenchmarkTest {
@Rule
public TestRule benchmarkRun = new BenchmarkRule();
public static Integer[] ARRAY;
/** Prepare random numbers for tests. */
@BeforeClass
public static void beforeClass() {
ARRAY = new Integer[10000000];
for (int i = 0; i < 10000000; i++)
ARRAY[i] = i % 2 == 0 ? null : i;
}
@Test
public void test1() throws Exception {
TestClass x = new TestClass();
for (int a = 0; a < ARRAY.length; a++) {
x.test1(ARRAY[a]);
}
}
@Test
public void test2() throws Exception {
TestClass x = new TestClass();
for (int a = 0; a < ARRAY.length; a++) {
x.test2(ARRAY[a]);
}
}
If you want to use the more readable way like the Optional
provides, but also want to control JIT. You coould write a simple class that JIT can easily inline.
package util;
public class OptionalInteger {
public interface NullableInt {
public int orElse(int defaultValue);
}
public static NullableInt ofNullable(Integer integer) {
return new NullableInt() {
@Override
public int orElse(int elseValue) {
return integer == null ? elseValue : integer;
}
};
}
}
Your client code will almost look the same.
import static util.OptionalInteger.ofNullable;
public class TestClass {
int i;
public void test1(Integer x) {
i = ofNullable(x).orElse(-1);
}
}
But JIT easily recognizes that it can be inlined.
Here is the test code
class Main {
public static void main(String args[]) {
List<Integer> integers = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 100000000; i++) {
Integer inte = random.nextBoolean() ? random.nextInt() : null;
integers.add(inte);
}
TestClass testClass = new TestClass();
warmUpJIT(integers, testClass);
execWithMeasurement(testClass, integers);
}
private static void warmUpJIT(List<Integer> integers, TestClass testClass) {
exec(testClass, integers);
exec(testClass, integers);
exec(testClass, integers);
exec(testClass, integers);
}
private static void execWithMeasurement(TestClass testClass, List<Integer> integers) {
long start = System.currentTimeMillis();
int result = exec(testClass, integers);
long end = System.currentTimeMillis();
String msg = MessageFormat.format("Result {0} - took {1} ms", result, (end - start));
System.out.println(msg);
}
private static int exec(TestClass testClass, List<Integer> integers) {
int result = 0;
for (Integer integer : integers) {
testClass.test1(integer);
result += testClass.i;
}
return result;
}
}
I used JITclipse, an eclipse integration for JITWatch that I wrote some time ago.