Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is defaulting to an empty lambda better or worse than checking for a potentially null lambda?

I'm working on a small scene graph implementation in Java 8. The basic scene node looks something like this:

public class SceneNode {
  private final List<SceneNode> children = new ArrayList<>();
  protected Runnable preRender;
  protected Runnable postRender;
  protected Runnable render;

  public final void render() {
    preRender.run();
    render.run();
    for (Renderable child : children) {
      child.render();
    }
    postRender.run();
  }
}

This works fine if the Runnables default to () -> {}. However, alternatively I could allow them to be null, but that means that render() method has to look like this:

  public final void render() {
    if (null != preRender) { preRender.run(); }
    if (null != render) { render.run(); }
    for (Renderable child : children) {
      child.render();
    }
    if (null != postRender) { postRender.run(); }
  }

So my question is, is the implicit cost of the branching introduced by the null check likely to cost more or less than whatever the JVM ends up compiling an empty lambda into? It seems like it should end up costing more to check for null, because a potential branch limits optimization, while presumably the Java compiler or JVM should be smart enough to compile an empty lambda into a no-op.

like image 719
Jherico Avatar asked Apr 06 '14 05:04

Jherico


2 Answers

Interestingly, it seems that checking for null is a little bit faster, than calling an empty lambda or an empty anonymous class, when the JVM is run with the -client argument. When running with -server, the performance is the same for all approaches.

I have done a micro benchmark with Caliper, to test this.

Here is the test class (latest Caliper form git necessary to compile):

@VmOptions("-client")
public class EmptyLambdaTest {

    public Runnable emptyLambda = () -> {};
    public Runnable emptyAnonymousType = new Runnable() {
        @Override
        public void run() {}
    };

    public Runnable nullAbleRunnable;

    @Benchmark
    public int timeEmptyLambda(int reps){
        int dummy = 0;
        for (int i = 0; i < reps; i++) {
            emptyLambda.run();
            dummy |= i;
        }
        return dummy;
    }

    @Benchmark
    public int timeEmptyAnonymousType(int reps){
        int dummy = 0;
        for (int i = 0; i < reps; i++) {
            emptyAnonymousType.run();
            dummy |= i;
        }
        return dummy;
    }

    @Benchmark
    public int timeNullCheck(int reps){
        int dummy = 0;
        for (int i = 0; i < reps; i++) {
            if (nullAbleRunnable != null) {
                nullAbleRunnable.run();
            }
            dummy |= i;
        }
        return dummy;
    }

}

And here are the benchmark results:

  • Running with -client
  • Running with -server
like image 83
Balder Avatar answered Sep 29 '22 17:09

Balder


Is defaulting to an empty lambda better or worse than checking for a potentially null lambda?

This is essentially the same as asking if it is better to test for a null String parameter or try to substitute an empty String.

The answer is that it depends on whether you want to treat the null as a programming error ... or not.

My personal opinion is that unexpected nulls should be treated as programming errors, and that you should allow the program to crash with an NPE. That way, the problem will come to your attention earlier and will be easier to track down and fix ... than if you substituted some "make good" value to stop the NPE from being thrown.

But of course, that doesn't apply for expected null values; i.e. when the API javadocs say that a null is a permissible value, and say what it means.


This also relates to how you design your APIs. In this case, the issue is whether your API spec (i.e. the javadoc!) should insist on the programmer providing a no-op lambda, or treat null as meaning the same thing. That boils down to a compromise between:

  • API client convenience,
  • API implementor work, and
  • robustness; e.g. when using the value of an incorrectly initialized variable ...

I'm more concerned about the implications of the runtime performance of using an empty lambda vs using a null and having to do a null check.

My intuition is that testing for null would be faster, but any difference in performance will be small, and that the chances are that it won't be significant to the overall performance of the application.

(UPDATE - Turns out that my intuition is "half right" according to @Balder's micro-benchmarking. For a -client mode JVM, null checking is a bit faster, but not enough to be concerning. For a -server mode JVM, the JIT compiler is apparently optimizing both cases to native code with identical performance.)

I suggest that you treat that you would (or at least should) treat any potential optimization problem:

  1. Put off any optimization until your application is working.
  2. Benchmark the application to see if it is already fast enough
  3. Profile the applications to see where the real hotspots are
  4. Develop and test a putative optimization
  5. Rerun the benchmarks to see if it improved things
  6. Go to step 2.
like image 39
Stephen C Avatar answered Sep 29 '22 18:09

Stephen C