简体   繁体   English

违反一个空的lambda比检查一个潜在的null lambda更好还是更差?

[英]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: 我正在研究Java 8中的小型场景图实现。基本场景节点看起来像这样:

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 () -> {} . 如果Runnables默认为() -> {}这可以正常工作。 However, alternatively I could allow them to be null, but that means that render() method has to look like this: 但是,或者我可以允许它们为null,但这意味着render()方法必须如下所示:

  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? 所以我的问题是,null检查引入的分支的隐式成本是否可能比JVM最终编译空lambda的成本更高或更低? 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. 看起来它应该花费更多来检查null,因为潜在的分支限制了优化,而可能是Java编译器或JVM应该足够聪明以将空lambda编译成no-op。

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. 有趣的 ,当使用-client参数运行JVM时, 检查null似乎比调用空lambda或空匿名类快一点 When running with -server , the performance is the same for all approaches. 使用-server运行时,所有方法的性能都相同。

I have done a micro benchmark with Caliper , to test this. 我用Caliper做了一个微基准测试。

Here is the test class (latest Caliper form git necessary to compile): 这是测试类(编译时需要的最新Caliper表单git):

@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: 以下是基准测试结果:

Is defaulting to an empty lambda better or worse than checking for a potentially null lambda? 违反一个空的lambda比检查一个潜在的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 . 这与询问是否更好地测试null String参数或尝试替换空String一样基本相同。

The answer is that it depends on whether you want to treat the null as a programming error ... or not. 答案是,这取决于您是否要将 null视为编程错误......或者不是。

My personal opinion is that unexpected null s should be treated as programming errors, and that you should allow the program to crash with an NPE. 我个人认为, 意外的 null应该被视为编程错误,并且你应该允许程序与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. 这样,问题就会提前引起你的注意,并且更容易追踪和修复......而不是你用一些“做好”的价值来阻止NPE被抛出。

But of course, that doesn't apply for expected null values; 但是,当然,这不适用于预期的 null值; ie when the API javadocs say that a null is a permissible value, and say what it means. 即,当API javadocs说null是允许值时,并说出它意味着什么。


This also relates to how you design your APIs. 这也与您设计 API的方式有关。 In this case, the issue is whether your API spec (ie the javadoc!) should insist on the programmer providing a no-op lambda, or treat null as meaning the same thing. 在这种情况下,问题是你的API规范(即javadoc!)是否应该坚持程序员提供no-op lambda,或者将null视为同一个东西。 That boils down to a compromise between: 归结为以下两者之间的妥协:

  • API client convenience, API客户方便,
  • API implementor work, and API实现工作,和
  • robustness; 鲁棒性; eg 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. 我更关心使用空lambda与使用null并且必须进行空检查的运行时性能的含义。

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. 我的直觉是测试null会更快,但性能上的任何差异都会很小,并且很可能它对应用程序的整体性能不会很重要

( 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.) (UPDATE -原来,我的直觉是根据@巴尔德的微标杆“说对了一半”对于一个。 -client模式JVM,空检查是有点快,但还不足以描述有关对于一个。 -server模式JVM中, JIT编译器显然将两种情况都优化为具有相同性能的本机代码。)

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. 转到第2步。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM