好吧,OP的基准测试不是理想的基准测试。需要减轻许多影响,包括预热,消除死代码,分叉等。幸运的是,JMH已经处理了很多事情,并且具有Java和Scala的绑定。请按照JMH页面上的过程获取基准测试项目,然后可以将下面的基准测试移植。
这是样本Java基准测试:
@BenchmarkMode(Mode.AverageTime)@OutputTimeUnit(TimeUnit.MICROSECONDS)@State(Scope.Benchmark)@Fork(3)@Warmup(iterations = 5)@Measurement(iterations = 5)public class JavaBench { @Param({"1", "5", "10", "15", "20"}) int t; private int run() { int i = 10; while(!isEvenlyDivisible(2, i, t)) i += 2; return i; } private boolean isEvenlyDivisible(int i, int a, int b) { if (i > b) return true; else return (a % i == 0) && isEvenlyDivisible(i + 1, a, b); } @GenerateMicroBenchmark public int test() { return run(); }}…这是示例Scala基准测试:
@BenchmarkMode(Array(Mode.AverageTime))@OutputTimeUnit(TimeUnit.MICROSECONDS)@State(Scope.Benchmark)@Fork(3)@Warmup(iterations = 5)@Measurement(iterations = 5)class ScalaBench { @Param(Array("1", "5", "10", "15", "20")) var t: Int = _ private def run(): Int = { var i = 10 while(!isEvenlyDivisible(2, i, t)) i += 2 i } @tailrec private def isEvenlyDivisible(i: Int, a: Int, b: Int): Boolean = { if (i > b) true else (a % i == 0) && isEvenlyDivisible(i + 1, a, b) } @GenerateMicroBenchmark def test(): Int = { run() }}如果您在JDK 8 GA,Linux x86_64上运行它们,则将获得:
Benchmark (t) Mode Samples Mean Mean error Unitso.s.ScalaBench.test 1 avgt 15 0.005 0.000 us/opo.s.ScalaBench.test 5 avgt 15 0.489 0.001 us/opo.s.ScalaBench.test 10 avgt 15 23.672 0.087 us/opo.s.ScalaBench.test 15 avgt 15 3406.492 9.239 us/opo.s.ScalaBench.test 20 avgt 15 2483221.694 5973.236 us/opBenchmark (t) Mode Samples Mean Mean error Unitso.s.JavaBench.test 1 avgt 15 0.002 0.000 us/opo.s.JavaBench.test 5 avgt 15 0.254 0.007 us/opo.s.JavaBench.test 10 avgt 15 12.578 0.098 us/opo.s.JavaBench.test 15 avgt 15 1628.694 11.282 us/opo.s.JavaBench.test 20 avgt 15 1066113.157 11274.385 us/op
请注意,我们正在
t努力查看效果对于的特定值是否是局部的
t。并非如此,效果是系统的,而Java版本是两倍的速度。
PrintAssembly将对此有所启发。这是Scala基准测试中最热门的代码块:
0x00007fe759199d42: test %r8d,%r8d0x00007fe759199d45: je 0x00007fe759199d76 ;*irem ; - org.sample.ScalaBench::isEvenlyDivisible@11 (line 52) ; - org.sample.ScalaBench::run@10 (line 45)0x00007fe759199d47: mov %ecx,%eax0x00007fe759199d49: cmp $0x80000000,%eax0x00007fe759199d4e: jne 0x00007fe759199d580x00007fe759199d50: xor %edx,%edx0x00007fe759199d52: cmp $0xffffffffffffffff,%r8d0x00007fe759199d56: je 0x00007fe759199d5c0x00007fe759199d58: cltd 0x00007fe759199d59: idiv %r8d
…这与Java中的类似块相同:
0x00007f4a811848cf: movslq %ebp,%r100x00007f4a811848d2: mov %ebp,%r9d0x00007f4a811848d5: sar $0x1f,%r9d0x00007f4a811848d9: imul $0x55555556,%r10,%r100x00007f4a811848e0: sar $0x20,%r100x00007f4a811848e4: mov %r10d,%r11d0x00007f4a811848e7: sub %r9d,%r11d ;*irem ; - org.sample.JavaBench::isEvenlyDivisible@9 (line 63) ; - org.sample.JavaBench::isEvenlyDivisible@19 (line 63) ; - org.sample.JavaBench::run@10 (line 54)
请注意,在Java版本中,编译器如何使用技巧将整数余数计算转换为乘法和右移(请参阅Hacker’s
Delight,第10章,第19节)。当编译器检测到我们针对常数计算余数时,这是可能的,这表明Java版本达到了最佳效果,而Scala版本则没有。您可以深入研究字节码反汇编以弄清scalac中有哪些怪癖,但是此练习的目的是,基准测试大大放大了代码生成中令人惊讶的微小差异。
PS这么多
@tailrec…
更新:对效果的更彻底的解释:http : //shipilev.net/blog/2014/java-scala-divided-we-
fail/



