34

I just stumbled upon the Optional class in Java 8 - I really like the approach of replacing some of the null checks (which literally means "is the value present?") in my code with isPresent() method calls.

My question is: Wouldn't that cause a lower performance of my code? I am just guessing simple null checks could be a bit cheaper and I am not yet very good in byte code reading / interpretation, so I am really interested in your thoughts on that topic.

jod
  • 513
  • 1
  • 4
  • 9
  • 3
    Why don't you benchmark it? – Vince Jan 09 '16 at 17:54
  • 3
    you shouldn't do `isPresent`, instead use `map` and `orElse`. – Łukasz Jan 09 '16 at 17:54
  • 4
    @Łukasz: That needs justification. It's true sometimes, but if you want to do a side-effect operation when the value is present, what do you do if not `if (isPresent()) doSomething()`? Neither map nor orElse make sense there. – Mark Peters Jan 09 '16 at 17:57
  • you have `ifPresent` method for that :p Anyway, it is general advice not a law. – Łukasz Jan 09 '16 at 17:58
  • 3
    The performance of code is never about the speed of null checks, so it doesn't even matter. – Kayaman Jan 09 '16 at 18:02
  • Thanks for your thoughts so far - I really appreciate that. – jod Jan 09 '16 at 18:52
  • @Vince Emigh I implemented a small benchmark for it, but I was pretty unsure if my scenario has any significance - I was just interested in the thoughts of other Java programmers who maybe experienced a performance penalty when using Optional in some scenarios. – jod Jan 09 '16 at 18:52
  • @Kayaman I see your point - my definition of "lower performance" in that case is just based on the guess that the indirection caused by Optional class usage would lead to more byte code which needs to be executed (so: eventually uses more time than a simple null check if called many times). But I guess I should have pointed that out in my question. – jod Jan 09 '16 at 18:52
  • 1
    @jod What I meant was that in real life situations it doesn't matter one bit (no pun intended). The increased correctness and readability provided by `Optional` is far more important than the number of bytecodes, unless you're working in a very specific environment. – Kayaman Jan 09 '16 at 18:55
  • @Kayaman Ok, I see. And I absolutely agree to the readability point. (nice pun btw ;-) ) – jod Jan 09 '16 at 19:03
  • I'm interested in the GC overhead associated with allocating and throwing away `Optional` objects over time if anyone has measurements related to that (microbenchmarks will do). – jocull Jan 30 '19 at 16:25

3 Answers3

27

I did some performance testing using an algorithm that heavily uses null checks as well as access to a potentially nullable field. I implemented a simple algorithm that removes the middle element from the single linked list.

First I implemented two classes of linked list node: safe - with Optional and unsafe - without.

Safe Node

class Node<T> {
    private final T data;
    private Optional<Node<T>> next = Optional.empty();

    Node(T data) {

        this.data = data;
    }

    Optional<Node<T>> getNext() {
        return next;
    }

    void setNext(Node<T> next) { setNext(Optional.ofNullable(next)); }

    void setNext(Optional<Node<T>> next ) { this.next = next; }
}

Unsafe Node

class NodeUnsafe<T> {
    private final T data;
    private NodeUnsafe<T> next;

    NodeUnsafe(T data) {
        this.data = data;
    }

    NodeUnsafe<T> getNext() {
        return next;
    }

    void setNext(NodeUnsafe<T> next) {
        this.next = next;
    }
}

Then I implemented two similar methods with the only difference - first uses Node<T> and the second uses NodeUsafe<T>

class DeleteMiddle {
    private static <T> T getLinkedList(int size, Function<Integer, T> supplier, BiConsumer<T, T> reducer) {
        T head = supplier.apply(1);
        IntStream.rangeClosed(2, size).mapToObj(supplier::apply).reduce(head,(a,b)->{
            reducer.accept(a,b);
            return b;
        });
        return head;
    }

    private static void deleteMiddle(Node<Integer> head){
        Optional<Node<Integer>> oneStep = Optional.of(head);
        Optional<Node<Integer>> doubleStep = oneStep;
        Optional<Node<Integer>> prevStep = Optional.empty();

        while (doubleStep.isPresent() && doubleStep.get().getNext().isPresent()){
            doubleStep = doubleStep.get().getNext().get().getNext();
            prevStep = oneStep;
            oneStep = oneStep.get().getNext();
        }

        final Optional<Node<Integer>> toDelete = oneStep;
        prevStep.ifPresent(s->s.setNext(toDelete.flatMap(Node::getNext)));
    }

    private static void deleteMiddleUnsafe(NodeUnsafe<Integer> head){
        NodeUnsafe<Integer> oneStep = head;
        NodeUnsafe<Integer> doubleStep = oneStep;
        NodeUnsafe<Integer> prevStep = null;

        while (doubleStep != null && doubleStep.getNext() != null){
            doubleStep = doubleStep.getNext().getNext();
            prevStep = oneStep;
            oneStep = oneStep.getNext();
        }
        if (prevStep != null) {
            prevStep.setNext(oneStep.getNext());
        }
    }

    public static void main(String[] args) {
        int size = 10000000;
        Node<Integer> head = getLinkedList(size, Node::new, Node::setNext);
        Long before = System.currentTimeMillis();
        deleteMiddle(head);
        System.out.println("Safe: " +(System.currentTimeMillis() - before));

        NodeUnsafe<Integer> headUnsafe = getLinkedList(size, NodeUnsafe::new, NodeUnsafe::setNext);
        before = System.currentTimeMillis();
        deleteMiddleUnsafe(headUnsafe);
        System.out.println("Unsafe: " +(System.currentTimeMillis() - before));
    }
}

Comparison of two several runs with different size of the list shows that approach with code that uses Optionalat the best is twice slower than one with nullables. With small lists it is 3 times slower.

Enigo
  • 3,685
  • 5
  • 29
  • 54
Yuriy Tseretyan
  • 1,676
  • 16
  • 17
  • I was expecting this, since an Optional is an additional object that has to be created...and even empty Objects use size, object header size and others – Diego Ramos May 10 '21 at 17:49
22

Optional<T> is just a normal generic class which contains a reference of type T. Thus, it adds a single layer of indirection. The method calls themselves won't be very expensive either, since the class is final and so the dynamic dispatch can be avoided.

The only place where you could have performance problems is when working with very large numbers of such instances, but even then the performance of something like a Stream<Optional<String>> is not bad at all. However, when working with large amounts of primitive values, you'll find a performance hit using Stream<Integer> (or Integer[]) versus the primitive specialization IntStream (or int[]) due to this layer of indirection requiring very frequent instantiation of Integer objects. However, this is a penalty that we already know and do pay when using things like ArrayList<Integer>.

You would obviously experience the same hit with Stream<OptionalInt> / OptionalInt[], since an OptionalInt is basically a class with an int field and a boolean flag for presence (unlike with Optional<T> which can make do with only the T field) and thus quite similar to Integer although bigger in size. And of course, a Stream<Optional<Integer>> would add two levels of indirection, with the corresponding double performance penalty.

Javier Martín
  • 2,537
  • 10
  • 15
0

We bench marked the below code using openjdk .

sc.map(MYObject::getRequest)
  .map(RequestDO::getMyInst)
  .map(MyInstDO::getCar)
  .map(CarDO::getId); 

if(id.isPresent())

OR

if( null != MYObject.getRequest() && null != 
    MYObject.getRequest().getMyInst() && null != 
    MYObject.getRequest().getMyInst().getCar() && null != 
    MYObject.getRequest().getMyInst().getCar().getId() )

And the result shows Optional is far better than traditional not null check.

Benchmark                     Mode     Cnt        Score    Error   Units

JMHBMarkModes.measureNotNull  thrpt    5          0.149    ± 0.036  ops/us
JMHBMarkModes.measureOptional thrpt    5         11.418    ± 1.140  ops/us
JMHBMarkModes.measureNotNull  avgt     5         12.342    ± 8.334  us/op
JMHBMarkModes.measureOptional avgt     5          0.088    ± 0.010  us/op

But if your use case is like (null != MYObject.getRequest()) , then not null check is better. So Optional performance depends on the use case you have .

Manuel Jordan
  • 15,253
  • 21
  • 95
  • 158
jAvA
  • 557
  • 1
  • 8
  • 22
  • 1
    Exactly the benchmark i was looking for. But what means thrpt and avgt? Score are ms? And Error is what? – Momo Jul 18 '18 at 12:14
  • 1
    thrpt - Throughput measures the number of operations per second . avgt - Average Time – jAvA Jul 27 '18 at 00:50
  • I wonder what the result would be if you OR'ed a series of checks for null then negated it instead of AND'ing a series of checks for not null? The compiler might be smart enough to do that for you, but I'm wondering if by using AND you force the program to execute every single one of those tests rather than simply bailing out at the first sign of a null value... – Bradley McKinley Oct 09 '18 at 21:51
  • 3
    @BradleyMcKinley of course, the evaluation of the compound condition stops at the first encountered `null`. Otherwise, the next condition would cause a `NullPointerException`. Still, I would not agree that this blatant code duplication (invoking `getRequest()` four times, `getMyInst()` three times, etc) was the “traditional not null check”. However, this benchmarks lacks any context; how expensive were the methods, how often did they return `null`/non-`null` during the test, etc… – Holger Jan 31 '19 at 17:28
  • 1
    could you share this benchmark (whole code)? Can't get anything even close to such results, and code without Optionals is always multiple time faster – GotoFinal Jan 14 '20 at 13:20