(修改后的答案.)
用Java进行基准测试很困难.不过,让我们把JMH抛给它......我将你的基准移植到JMH(参见http://github.com/lemire/microbenchmarks).
这些是相关方法......
public FooPrime[] basicstream(BenchmarkState s) { return (FooPrime[]) s.fooList.stream().map(it -> { return new FooPrime().gamma(it.getAlpha() + it.getBeta()); }).toArray(FooPrime[]::new); } public FooPrime[] tweakedbasicstream(BenchmarkState s) { return (FooPrime[]) s.fooList.stream().map(it -> { int stuff = it.getAlpha().length(); return new FooPrime().gamma(it.getAlpha() + it.getBeta()); }).toArray(FooPrime[]::new); }
这是我跑步的结果......
git clone https://github.com/lemire/microbenchmarks.git cd microbenchmarks mvn clean install java -cp target/microbenchmarks-0.0.1-jar-with-dependencies.jar me.lemire.microbenchmarks.mysteries.MysteriousLambda Benchmark Mode Samples Score Error Units m.l.m.m.MysteriousLambda.basicstream avgt 5 17013.784 ± 46.536 ns/op m.l.m.m.MysteriousLambda.tweakedbasicstream avgt 5 16240.451 ± 67.884 ns/op
奇怪的是,似乎这两个函数并没有以完全相同的平均速度运行,存在相当显着的差异.这是在使用JMH时,这是一个相当不错的基准测试框架.
我一开始以为你的两段代码在逻辑上是等价的,但它们不是.当返回的String对象为null时,显然无用的长度方法访问会强制代码抛出异常.
所以它实际上更接近以下代码...
@Benchmark public FooPrime[] nullbasicstream(BenchmarkState s) { return (FooPrime[]) s.fooList.stream().map(it -> { if( it.getAlpha() == null) throw new NullPointerException(); return new FooPrime().gamma(it.getAlpha() + it.getBeta()); }).toArray(FooPrime[]::new); }
这甚至比你的调整功能更快......
Benchmark Mode Samples Score Error Units m.l.m.m.MysteriousLambda.basicstream avgt 5 17013.784 ± 46.536 ns/op m.l.m.m.MysteriousLambda.nullbasicstream avgt 5 15983.762 ± 92.593 ns/op m.l.m.m.MysteriousLambda.tweakedbasicstream avgt 5 16240.451 ± 67.884 ns/op
为什么会这样?
让我们回避Java 8的流编程并用愚蠢的旧方式编写函数,有无空检查:
@Benchmark public FooPrime[] basicsum(BenchmarkState s) { int howmany = s.fooList.size(); FooPrime[] answer = new FooPrime[s.fooList.size()]; for(int k = 0; k < howmany ; ++k ) { Foo x = s.fooList.get(k); answer[k] = new FooPrime(x.getAlpha() + x.getBeta()); } return answer; } @Benchmark public FooPrime[] basicsumnull(BenchmarkState s) { int howmany = s.fooList.size(); FooPrime[] answer = new FooPrime[s.fooList.size()]; for(int k = 0; k < howmany ; ++k ) { Foo x = s.fooList.get(k); if(x.getAlpha() == null) throw new NullPointerException(); answer[k] = new FooPrime(x.getAlpha() + x.getBeta()); } return answer; }
这就是我们如何获得最佳表现......
m.l.m.m.MysteriousLambda.basicstream avgt 5 17019.730 ± 61.982 ns/op m.l.m.m.MysteriousLambda.nullbasicstream avgt 5 16019.332 ± 62.831 ns/op m.l.m.m.MysteriousLambda.basicsum avgt 5 15635.474 ± 119.890 ns/op m.l.m.m.MysteriousLambda.basicsumnull avgt 5 14342.016 ± 109.958 ns/op
但空检查的好处仍然存在.
好.让我们只对字符串总和进行基准测试,而不需要任何其他东西(没有自定义类).让我们得到标准和和以及空检查之前的总和:
@Benchmark public void stringsum(BenchmarkState s) { for(int k = 0; k < s.N; ++k) s.list3[k] = s.list1[k] + s.list2[k]; } @Benchmark public void stringsum_withexcept(BenchmarkState s) { for(int k = 0; k < s.N; ++k) { if(s.list1[k] == null) throw new NullPointerException(); s.list3[k] = s.list1[k] + s.list2[k]; } }
我们得到null检查减慢了我们...
m.l.m.m.StringMerge.stringsum avgt 5 27011.111 ± 4.077 ns/op m.l.m.m.StringMerge.stringsum_withexcept avgt 5 28387.825 ± 82.523 ns/op