Phi*_*hil 53 java performance jvm scala microbenchmark
我遇到了这个老问题,并使用scala 2.10.3进行了以下实验.
我重写了Scala版本以使用显式尾递归:
import scala.annotation.tailrec
object ScalaMain {
private val t = 20
private def run() {
var i = 10
while(!isEvenlyDivisible(2, i, t))
i += 2
println(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)
}
def main(args: Array[String]) {
val t1 = System.currentTimeMillis()
var i = 0
while (i < 20) {
run()
i += 1
}
val t2 = System.currentTimeMillis()
println("time: " + (t2 - t1))
}
}
Run Code Online (Sandbox Code Playgroud)
并将其与以下Java版本进行比较.为了与Scala公平比较,我有意识地使函数非静态:
public class JavaMain {
private final int t = 20;
private void run() {
int i = 10;
while (!isEvenlyDivisible(2, i, t))
i += 2;
System.out.println(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);
}
public static void main(String[] args) {
JavaMain o = new JavaMain();
long t1 = System.currentTimeMillis();
for (int i = 0; i < 20; ++i)
o.run();
long t2 = System.currentTimeMillis();
System.out.println("time: " + (t2 - t1));
}
}
Run Code Online (Sandbox Code Playgroud)
以下是我的计算机上的结果:
> java JavaMain
....
time: 9651
> scala ScalaMain
....
time: 20592
Run Code Online (Sandbox Code Playgroud)
这是scala 2.10.3 on(Java HotSpot(TM)64位服务器VM,Java 1.7.0_51).
我的问题是scala版本的隐藏成本是多少?
非常感谢.
Ale*_*lev 128
那么,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();
}
}
Run Code Online (Sandbox Code Playgroud)
......这是示例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()
}
}
Run Code Online (Sandbox Code Playgroud)
如果你在JDK 8 GA,Linux x86_64上运行它们,那么你会得到:
Benchmark (t) Mode Samples Mean Mean error Units
o.s.ScalaBench.test 1 avgt 15 0.005 0.000 us/op
o.s.ScalaBench.test 5 avgt 15 0.489 0.001 us/op
o.s.ScalaBench.test 10 avgt 15 23.672 0.087 us/op
o.s.ScalaBench.test 15 avgt 15 3406.492 9.239 us/op
o.s.ScalaBench.test 20 avgt 15 2483221.694 5973.236 us/op
Benchmark (t) Mode Samples Mean Mean error Units
o.s.JavaBench.test 1 avgt 15 0.002 0.000 us/op
o.s.JavaBench.test 5 avgt 15 0.254 0.007 us/op
o.s.JavaBench.test 10 avgt 15 12.578 0.098 us/op
o.s.JavaBench.test 15 avgt 15 1628.694 11.282 us/op
o.s.JavaBench.test 20 avgt 15 1066113.157 11274.385 us/op
Run Code Online (Sandbox Code Playgroud)
请注意,我们t要调查效果是否为特定值的局部效果t.它不是,效果是系统的,Java版本的速度是其两倍.
PrintAssembly将对此有所了解.这是Scala基准测试中最热门的一个:
0x00007fe759199d42: test %r8d,%r8d
0x00007fe759199d45: je 0x00007fe759199d76 ;*irem
; - org.sample.ScalaBench::isEvenlyDivisible@11 (line 52)
; - org.sample.ScalaBench::run@10 (line 45)
0x00007fe759199d47: mov %ecx,%eax
0x00007fe759199d49: cmp $0x80000000,%eax
0x00007fe759199d4e: jne 0x00007fe759199d58
0x00007fe759199d50: xor %edx,%edx
0x00007fe759199d52: cmp $0xffffffffffffffff,%r8d
0x00007fe759199d56: je 0x00007fe759199d5c
0x00007fe759199d58: cltd
0x00007fe759199d59: idiv %r8d
Run Code Online (Sandbox Code Playgroud)
...这是Java中类似的块:
0x00007f4a811848cf: movslq %ebp,%r10
0x00007f4a811848d2: mov %ebp,%r9d
0x00007f4a811848d5: sar $0x1f,%r9d
0x00007f4a811848d9: imul $0x55555556,%r10,%r10
0x00007f4a811848e0: sar $0x20,%r10
0x00007f4a811848e4: mov %r10d,%r11d
0x00007f4a811848e7: 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)
Run Code Online (Sandbox Code Playgroud)
请注意,在Java版本中,编译器如何使用技巧将整数余数计算转换为乘法和右移(参见Hacker's Delight,Ch.10,Sect.19).当编译器检测到我们根据常量计算余数时,这是可能的,这表明Java版本达到了甜蜜的优化,但Scala版本却没有.您可以深入研究字节码反汇编以找出scalac中干预的怪癖,但本练习的重点是代码生成中令人惊讶的微小差异被基准放大了很多.
PS那么多@tailrec......
更新:对效果的更全面的解释:http://shipilev.net/blog/2014/java-scala-divided-we-fail/
Ber*_*ium 23
我改变了 val
private val t = 20
Run Code Online (Sandbox Code Playgroud)
一个恒定的定义
private final val t = 20
Run Code Online (Sandbox Code Playgroud)
并且得到了显着的性能提升,现在似乎两个版本的表现几乎相同[在我的系统上,请参阅更新和评论].
我没有研究过字节码,但如果使用val t = 20你可以看到使用javap它有一个方法(而且那个版本和那个一样慢private val).
所以我假设即使private val涉及调用方法,也不能直接与finalJava中的方法进行比较.
更新
在我的系统上,我得到了这些结果
Java版本:时间:14725
Scala版本:时间:13228
在32位Linux上使用OpenJDK 1.7.
根据我的经验,64位系统上的Oracle JDK实际上表现更好,因此这可能解释了其他测量结果会产生更好的结果,有利于Scala版本.
至于Scala版本表现更好我假设尾递归优化确实在这里产生影响(参见Phil的答案,如果Java版本被重写为使用循环而不是递归,它会再次执行).
我查看了这个问题并编辑了Scala版本t内部run:
object ScalaMain {
private def run() {
val t = 20
var i = 10
while(!isEvenlyDivisible(2, i, t))
i += 2
println(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)
}
def main(args: Array[String]) {
val t1 = System.currentTimeMillis()
var i = 0
while (i < 20) {
run()
i += 1
}
val t2 = System.currentTimeMillis()
println("time: " + (t2 - t1))
}
}
Run Code Online (Sandbox Code Playgroud)
新的Scala版本现在运行速度是原始Java版本的两倍:
> fsc ScalaMain.scala
> scala ScalaMain
....
time: 6373
> fsc -optimize ScalaMain.scala
....
time: 4703
Run Code Online (Sandbox Code Playgroud)
我发现这是因为Java没有尾调用.优化的Java with loop而不是递归运行速度同样快:
public class JavaMain {
private static final int t = 20;
private void run() {
int i = 10;
while (!isEvenlyDivisible(i, t))
i += 2;
System.out.println(i);
}
private boolean isEvenlyDivisible(int a, int b) {
for (int i = 2; i <= b; ++i) {
if (a % i != 0)
return false;
}
return true;
}
public static void main(String[] args) {
JavaMain o = new JavaMain();
long t1 = System.currentTimeMillis();
for (int i = 0; i < 20; ++i)
o.run();
long t2 = System.currentTimeMillis();
System.out.println("time: " + (t2 - t1));
}
}
Run Code Online (Sandbox Code Playgroud)
现在我的困惑完全解决了:
> java JavaMain
....
time: 4795
Run Code Online (Sandbox Code Playgroud)
总之,最初的版本斯卡拉是缓慢的,因为我没有申报t是final(直接或间接地为铍的答案指出).由于缺少尾调用,原始Java版本很慢.
| 归档时间: |
|
| 查看次数: |
8617 次 |
| 最近记录: |