Hidden performance cost in Scala?

后端 未结 4 1632
傲寒
傲寒 2020-12-12 11:11

I came across this old question and did the following experiment with scala 2.10.3.

I rewrote the Scala version to use explicit tail recursion:

impor         


        
4条回答
  •  刺人心
    刺人心 (楼主)
    2020-12-12 12:11

    I looked at this question and edited the Scala version to have t inside 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))
      }
    }
    

    The new Scala version now runs twice as fast as the original Java one:

    > fsc ScalaMain.scala
    > scala ScalaMain
    ....
    time: 6373
    > fsc -optimize ScalaMain.scala
    ....
    time: 4703
    

    I figured out it is because Java not having tail calls. The optimized Java one with loop instead of recursion runs just as fast:

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

    Now my confusion is fully solved:

    > java JavaMain
    ....
    time: 4795
    

    In conclusion, the original Scala version was slow because I didn't declare t to be final (directly or indirectly, as Beryllium's answer points out). And the original Java version was slow due to lack of tail calls.

提交回复
热议问题