Scala recursion vs loop: performance and runtime considerations

前端 未结 3 1981
时光取名叫无心
时光取名叫无心 2021-02-06 01:17

I\'ve wrote a naïve test-bed to measure the performance of three kinds of factorial implementation: loop based, non tail-recursive and tail-recursive.

Su

3条回答
  •  清酒与你
    2021-02-06 02:21

    Scala static methods for factorial(n) (coded with scala 2.12.x, java-8):

    object Factorial {
    
      /*
       * For large N, it throws a stack overflow
       */
      def recursive(n:BigInt): BigInt = {
        if(n < 0) {
          throw new ArithmeticException
        } else if(n <= 1) {
          1
        } else {
          n * recursive(n - 1)
        }
      }
    
      /*
       * A tail recursive method is compiled to avoid stack overflow
       */
      @scala.annotation.tailrec
      def recursiveTail(n:BigInt, acc:BigInt = 1): BigInt = {
        if(n < 0) {
          throw new ArithmeticException
        } else if(n <= 1) {
          acc
        } else {
          recursiveTail(n - 1, n * acc)
        }
      }
    
      /*
       * A while loop
       */
      def loop(n:BigInt): BigInt = {
        if(n < 0) {
          throw new ArithmeticException
        } else if(n <= 1) {
          1
        } else {
          var acc = 1
          var idx = 1
          while(idx <= n) {
            acc = idx * acc
            idx += 1
          }
          acc
        }
      }
    
    }
    

    Specs:

    class FactorialSpecs extends SpecHelper {
    
      private val smallInt = 10
      private val largeInt = 10000
    
      describe("Factorial.recursive") {
        it("return 1 for 0") {
          assert(Factorial.recursive(0) == 1)
        }
        it("return 1 for 1") {
          assert(Factorial.recursive(1) == 1)
        }
        it("return 2 for 2") {
          assert(Factorial.recursive(2) == 2)
        }
        it("returns a result, for small inputs") {
          assert(Factorial.recursive(smallInt) == 3628800)
        }
        it("throws StackOverflow for large inputs") {
          intercept[java.lang.StackOverflowError] {
            Factorial.recursive(Int.MaxValue)
          }
        }
      }
    
      describe("Factorial.recursiveTail") {
        it("return 1 for 0") {
          assert(Factorial.recursiveTail(0) == 1)
        }
        it("return 1 for 1") {
          assert(Factorial.recursiveTail(1) == 1)
        }
        it("return 2 for 2") {
          assert(Factorial.recursiveTail(2) == 2)
        }
        it("returns a result, for small inputs") {
          assert(Factorial.recursiveTail(smallInt) == 3628800)
        }
        it("returns a result, for large inputs") {
          assert(Factorial.recursiveTail(largeInt).isInstanceOf[BigInt])
        }
      }
    
      describe("Factorial.loop") {
        it("return 1 for 0") {
          assert(Factorial.loop(0) == 1)
        }
        it("return 1 for 1") {
          assert(Factorial.loop(1) == 1)
        }
        it("return 2 for 2") {
          assert(Factorial.loop(2) == 2)
        }
        it("returns a result, for small inputs") {
          assert(Factorial.loop(smallInt) == 3628800)
        }
        it("returns a result, for large inputs") {
          assert(Factorial.loop(largeInt).isInstanceOf[BigInt])
        }
      }
    }
    

    Benchmarks:

    import org.scalameter.api._
    
    class BenchmarkFactorials extends Bench.OfflineReport {
    
      val gen: Gen[Int] = Gen.range("N")(1, 1000, 100) // scalastyle:ignore
    
      performance of "Factorial" in {
        measure method "loop" in {
          using(gen) in {
            n => Factorial.loop(n)
          }
        }
        measure method "recursive" in {
          using(gen) in {
            n => Factorial.recursive(n)
          }
        }
        measure method "recursiveTail" in {
          using(gen) in {
            n => Factorial.recursiveTail(n)
          }
        }
      }
    
    }
    

    Benchmark results (loop is much faster):

    [info] Test group: Factorial.loop
    [info] - Factorial.loop.Test-9 measurements:
    [info]   - at N -> 1: passed
    [info]     (mean = 0.01 ms, ci = <0.00 ms, 0.02 ms>, significance = 1.0E-10)
    [info]   - at N -> 101: passed
    [info]     (mean = 0.01 ms, ci = <0.01 ms, 0.01 ms>, significance = 1.0E-10)
    [info]   - at N -> 201: passed
    [info]     (mean = 0.02 ms, ci = <0.02 ms, 0.02 ms>, significance = 1.0E-10)
    [info]   - at N -> 301: passed
    [info]     (mean = 0.03 ms, ci = <0.02 ms, 0.03 ms>, significance = 1.0E-10)
    [info]   - at N -> 401: passed
    [info]     (mean = 0.03 ms, ci = <0.03 ms, 0.04 ms>, significance = 1.0E-10)
    [info]   - at N -> 501: passed
    [info]     (mean = 0.04 ms, ci = <0.03 ms, 0.05 ms>, significance = 1.0E-10)
    [info]   - at N -> 601: passed
    [info]     (mean = 0.04 ms, ci = <0.04 ms, 0.05 ms>, significance = 1.0E-10)
    [info]   - at N -> 701: passed
    [info]     (mean = 0.05 ms, ci = <0.05 ms, 0.05 ms>, significance = 1.0E-10)
    [info]   - at N -> 801: passed
    [info]     (mean = 0.06 ms, ci = <0.05 ms, 0.06 ms>, significance = 1.0E-10)
    [info]   - at N -> 901: passed
    [info]     (mean = 0.06 ms, ci = <0.05 ms, 0.07 ms>, significance = 1.0E-10)
    
    [info] Test group: Factorial.recursive
    [info] - Factorial.recursive.Test-10 measurements:
    [info]   - at N -> 1: passed
    [info]     (mean = 0.00 ms, ci = <0.00 ms, 0.01 ms>, significance = 1.0E-10)
    [info]   - at N -> 101: passed
    [info]     (mean = 0.05 ms, ci = <0.01 ms, 0.09 ms>, significance = 1.0E-10)
    [info]   - at N -> 201: passed
    [info]     (mean = 0.03 ms, ci = <0.02 ms, 0.05 ms>, significance = 1.0E-10)
    [info]   - at N -> 301: passed
    [info]     (mean = 0.07 ms, ci = <0.00 ms, 0.13 ms>, significance = 1.0E-10)
    [info]   - at N -> 401: passed
    [info]     (mean = 0.09 ms, ci = <0.01 ms, 0.18 ms>, significance = 1.0E-10)
    [info]   - at N -> 501: passed
    [info]     (mean = 0.10 ms, ci = <0.03 ms, 0.17 ms>, significance = 1.0E-10)
    [info]   - at N -> 601: passed
    [info]     (mean = 0.11 ms, ci = <0.08 ms, 0.15 ms>, significance = 1.0E-10)
    [info]   - at N -> 701: passed
    [info]     (mean = 0.13 ms, ci = <0.11 ms, 0.14 ms>, significance = 1.0E-10)
    [info]   - at N -> 801: passed
    [info]     (mean = 0.16 ms, ci = <0.13 ms, 0.19 ms>, significance = 1.0E-10)
    [info]   - at N -> 901: passed
    [info]     (mean = 0.21 ms, ci = <0.15 ms, 0.27 ms>, significance = 1.0E-10)
    
    [info] Test group: Factorial.recursiveTail
    [info] - Factorial.recursiveTail.Test-11 measurements:
    [info]   - at N -> 1: passed
    [info]     (mean = 0.00 ms, ci = <0.00 ms, 0.01 ms>, significance = 1.0E-10)
    [info]   - at N -> 101: passed
    [info]     (mean = 0.04 ms, ci = <0.03 ms, 0.05 ms>, significance = 1.0E-10)
    [info]   - at N -> 201: passed
    [info]     (mean = 0.12 ms, ci = <0.05 ms, 0.20 ms>, significance = 1.0E-10)
    [info]   - at N -> 301: passed
    [info]     (mean = 0.16 ms, ci = <-0.03 ms, 0.34 ms>, significance = 1.0E-10)
    [info]   - at N -> 401: passed
    [info]     (mean = 0.12 ms, ci = <0.09 ms, 0.16 ms>, significance = 1.0E-10)
    [info]   - at N -> 501: passed
    [info]     (mean = 0.17 ms, ci = <0.15 ms, 0.19 ms>, significance = 1.0E-10)
    [info]   - at N -> 601: passed
    [info]     (mean = 0.23 ms, ci = <0.19 ms, 0.26 ms>, significance = 1.0E-10)
    [info]   - at N -> 701: passed
    [info]     (mean = 0.25 ms, ci = <0.18 ms, 0.32 ms>, significance = 1.0E-10)
    [info]   - at N -> 801: passed
    [info]     (mean = 0.28 ms, ci = <0.21 ms, 0.36 ms>, significance = 1.0E-10)
    [info]   - at N -> 901: passed
    [info]     (mean = 0.32 ms, ci = <0.17 ms, 0.46 ms>, significance = 1.0E-10)
    

提交回复
热议问题