Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java Scala中隐藏的性能成本?_Java_Performance_Scala_Jvm_Microbenchmark - Fatal编程技术网

Java Scala中隐藏的性能成本?

Java Scala中隐藏的性能成本?,java,performance,scala,jvm,microbenchmark,Java,Performance,Scala,Jvm,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 priv

我遇到了这个问题,并用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))
  }
}
这是Scala2.10.3(JavaHotSpot(TM)64位服务器虚拟机,Java1.7.0_51)

我的问题是scala版本的隐藏成本是多少


非常感谢。

我更改了
val

private val t = 20
一个恒定的定义

并且获得了显著的性能提升,现在看来这两个版本的性能几乎相同[在我的系统上,请参阅更新和注释]

我没有研究字节码,但是如果你使用
valt=20
,你可以看到使用
javap
有一种方法(这个版本和使用
private val
的版本一样慢)

因此,我假设即使是
private val
也需要调用一个方法,这与Java中的
final
没有直接的可比性

更新

在我的系统中,我得到了这些结果

Java版本:时间:14725

Scala版本:时间:13228

在32位Linux上使用OpenJDK1.7

根据我的经验,Oracle的JDK在64位系统上的性能确实更好,因此这可能解释了其他度量方法产生的结果甚至比Scala版本更好


至于性能更好的Scala版本,我假设尾部递归优化在这里确实有效果(参见Phil的回答,如果Java版本被重写为使用循环而不是递归,那么它的性能将再次相同)。

要使Java版本完全等同于Scala代码,您需要这样更改它

private int t = 20;


private int t() {
    return this.t;
}

private void run() {
    int i = 10;
    while (!isEvenlyDivisible(2, i, t()))
        i += 2;
    System.out.println(i);
}
速度较慢,因为JVM无法优化方法调用。

我查看并编辑了Scala版本,使其在
t
内部运行

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))
  }
}
我发现这是因为Java没有尾部调用。带循环而非递归的优化Java one运行速度同样快:

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));
    }
}

总之,最初的Scala版本很慢,因为我没有将
t
声明为
final
(正如s所指出的,直接或间接)。由于缺少尾部调用,最初的Java版本速度很慢。

好吧,OP的基准测试不是理想的。需要减轻大量的影响,包括预热、死代码消除、分叉等。幸运的是,它已经处理了很多事情,并且对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();
    }

}
@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()
  }

}
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
…这是Scala基准测试的示例:

@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();
    }

}
@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()
  }

}
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
如果您在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
请注意,我们对
t
进行杂耍,以查看对
t
的特定值的影响是否是局部的。事实并非如此,效果是系统性的,Java版本的速度是前者的两倍

将对此有一些启示。这是Scala基准测试中最热门的块:

@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();
    }

}
@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()
  }

}
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
…这是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)
请注意,在Java版本中,编译器是如何使用这种技巧将整数余数计算转换为乘法和右移的(请参阅《黑客的喜悦》,第10章,第19节)。当编译器检测到我们根据常量计算余数时,这是可能的,这表明Java版本达到了最佳优化,但Scala版本没有。您可以深入研究字节码反汇编,找出scalac中的哪些怪癖,但本练习的重点是,基准测试大大放大了代码生成中令人惊讶的细微差异

请注意,
@tailrec


更新:更全面的效果解释:

您能将其更改为与scala中的代码完全相同吗?例如,if-else语句可能不被视为与
中的三元运算符or完全相等。速度比较应该无关紧要,但代码现在并不完全相等。另外,是否先预热JVM,然后提供超过X次的平均运行数?预热JVM很重要。此外,反编译字节码可能会产生一些见解。我将每个基准测试运行了20次,并在Scala中使用
来避免闭包创建。根据经验,Scala每次输出之间的时间间隔也较长。考虑到您正在测试解释器,而不是JIT性能。。当然,scala代码的运行速度会变慢并不令人惊讶——这是一些额外的间接操作。现在如果我们正在测试实际的JIT代码?不太确定会有什么不同。@Voo:进行JITed的正确方法是什么?我编译了这两个文件并运行了生成的
.class
文件。无论如何,请查看我的更新结果的答案。实际上,您的新版本(以及将
t
移动到
run
内部的版本)在我的计算机上运行的速度是Java的两倍。如果有人能向我解释字节码中到底发生了什么,那就太好了。我对JVM没有经验,也不知道如何反编译字节码:)这也是我的印象,Scala版本的性能稍微好一点,可能尾部递归优化有效果。做了这样的更改,我得到了以下结果:Scala时间:7852 java时间:14657它不仅稍微快一点,而且快了两倍@pedrofurla我已将我的系统结果添加到答案中。但是是的,很高兴听到它在其他环境中有如此巨大的影响。有关
private final val
private val
之间区别的相关答案,请参阅。嘿,谢谢!这个新版本的Java比我原来的Java版本(在我的计算机上是11122ms)稍慢一些,但仍然比我原来的Scala版本快。但是,请查看速度奇快的Scala版本的更新。为什么在这里投票?这是一个正确的方法,然后是尾部递归优化。很高兴知道它按预期工作。为了完整性,您是否也尝试将
t
移动到
run