I was reading on the internet that returning Java Optional may be slow and it is not recommend for high-performance applications.
A bit of history
In Java lack of a value can be represented as null
or a more readable approach and less error-prone is to use Optional
. Using Optional
is the recommended way of writing code, beside making it clear for the method caller that value may be missing, it comes with a handy way of processing it using a functional style.
I would like to mention that if you don’t work in a extremely performance sensitive application you should use always Optional
to represent the lack of a value.
In order to measure the impact of Optional usage I did a test using JMH:
Sum a list of numbers using a primitive long
a Long
and an Optional<Long>
@State(Scope.Benchmark)
@Fork(1)
@Warmup(iterations = 2)
@Measurement(iterations = 5)
public class OptionBenchmark {
private final long MAGIC_NUMBER = 7;
// Variant 1.
// Probably the simplest way to sum numbers.
// No boxing, no objects involved, just primitive long values everywhere.
private long getNumber(long i) {
return i & 0xFF;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public long sumSimple() {
long sum = 0;
for (long i = 0; i < 1_000_000; ++i) {
long n = getNumber(i);
if (n != MAGIC_NUMBER)
sum += n;
}
return sum;
}
// Variant 2.
// Replace MAGIC_NUMBER with a null.
// To be able to return null, we need to box long into a Long object.
private Long getNumberOrNull(long i) {
long n = i & 0xFF;
return n == MAGIC_NUMBER ? null : n;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public long sumNulls() {
long sum = 0;
for (long i = 0; i < 1_000_000; ++i) {
Long n = getNumberOrNull(i);
if (n != null) {
sum += n;
}
}
return sum;
}
// Variant 3.
// Replace MAGIC_NUMBER with Optional.empty().
// Now we not only need to box the value into a Long, but also create the Optional wrapper.
private Optional<Long> getOptionalNumber(long i) {
long n = i & 0xFF;
return n == MAGIC_NUMBER ? Optional.empty() : Optional.of(n);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public long sumOptional() {
long sum = 0;
for (long i = 0; i < 1_000_000; ++i) {
Optional<Long> n = getOptionalNumber(i);
if (n.isPresent()) {
sum += n.get();
}
}
return sum;
}
}
Here are the results that I have obtained on my laptop:
OpenJDK 64-Bit Server VM GraalVM CE 20.3.0
Benchmark Mode Cnt Score Error Units
OptionBenchmark.sumNulls avgt 5 2753.341 ± 29.974 us/op
OptionBenchmark.sumOptional avgt 5 5989.606 ± 60.489 us/op
OptionBenchmark.sumSimple avgt 5 1192.625 ± 1.800 us/op
OpenJDK Runtime Environment (build 1.8.0_292-8u292-b10-0ubuntu1-b10)
Benchmark Mode Cnt Score Error Units
OptionBenchmark.sumNulls avgt 5 3386.756 ± 348.132 us/op
OptionBenchmark.sumOptional avgt 5 6647.280 ± 443.013 us/op
OptionBenchmark.sumSimple avgt 5 1200.177 ± 24.333 us/op
OpenJDK Runtime Environment (build 17+35-Ubuntu-121.04)
Benchmark Mode Cnt Score Error Units
OptionBenchmark.sumNulls avgt 5 1295.601 ± 33.755 us/op
OptionBenchmark.sumOptional avgt 5 5649.651 ± 358.539 us/op
OptionBenchmark.sumSimple avgt 5 617.378 ± 24.074 us/op
Conclusions
- Using
Optional
where performance is extremely important will make your code run slower - Using boxing where performance is extremely important isn’t probably the best idea
- Summing primitive
long
may be up to 9x more performant than usingOptional