2013-02-28 45 views
8

Tôi đã viết một bài kiểm tra ngây thơ để đo lường hiệu suất của ba loại thực hiện giai thừa: loop dựa, không đuôi đệ quy và đuôi đệ quy.Scala đệ quy vs vòng lặp: hiệu suất và thời gian chạy cân nhắc

Đáng ngạc nhiên với tôi performant tồi tệ nhất là những vòng lặp («khi» được dự kiến ​​sẽ có hiệu quả hơn vì vậy tôi cung cấp cả hai) rằng chi phí gần gấp đôi so với đuôi thay thế đệ quy.

ĐÁP: sửa chữa thực hiện vòng lặp tránh * = điều hành Mà làm tốt hơn tồi tệ nhất với bigint do ruột của nó «loops» trở thành nhanh nhất như mong đợi

Một «woodoo» hành vi của tôi đã trải nghiệm là trường hợp ngoại lệ StackOverflow không được ném sơ bộ cho cùng một mục nhập trong trường hợp trường hợp triển khai đệ quy không đuôi. Tôi có thể phá vỡ StackOverlow bằng việc tiếp tục gọi hàm với lớn hơn và lớn hơn giá trị ... Tôi cảm thấy điên :) Trả lời: JVM yêu cầu hội tụ trong quá trình startup, sau đó hành vi là chặt chẽ và có hệ thống

Đây là mã :

final object Factorial { 
    type Out = BigInt 

    def calculateByRecursion(n: Int): Out = { 
    require(n>0, "n must be positive") 

    n match { 
     case _ if n == 1 => return 1 
     case _ => return n * calculateByRecursion(n-1) 
    } 
    } 

    def calculateByForLoop(n: Int): Out = { 
    require(n>0, "n must be positive") 

    var accumulator: Out = 1 
    for (i <- 1 to n) 
     accumulator = i * accumulator 
    accumulator 
    } 

    def calculateByWhileLoop(n: Int): Out = { 
    require(n>0, "n must be positive") 

    var accumulator: Out = 1 
    var i = 1 
    while (i <= n) { 
     accumulator = i * accumulator 
     i += 1 
    } 
    accumulator 
    } 

    def calculateByTailRecursion(n: Int): Out = { 
    require(n>0, "n must be positive") 

    @tailrec def fac(n: Int, acc: Out): Out = n match { 
     case _ if n == 1 => acc 
     case _ => fac(n-1, n * acc) 
    } 

    fac(n, 1) 
    } 

    def calculateByTailRecursionUpward(n: Int): Out = { 
    require(n>0, "n must be positive") 

    @tailrec def fac(i: Int, acc: Out): Out = n match { 
     case _ if i == n => n * acc 
     case _ => fac(i+1, i * acc) 
    } 

    fac(1, 1) 
    } 

    def comparePerformance(n: Int) { 
    def showOutput[A](msg: String, data: (Long, A), showOutput:Boolean = false) = 
     showOutput match { 
     case true => printf("%s returned %s in %d ms\n", msg, data._2.toString, data._1) 
     case false => printf("%s in %d ms\n", msg, data._1) 
    } 
    def measure[A](f:()=>A): (Long, A) = { 
     val start = System.currentTimeMillis 
     val o = f() 
     (System.currentTimeMillis - start, o) 
    } 
    showOutput ("By for loop", measure(()=>calculateByForLoop(n))) 
    showOutput ("By while loop", measure(()=>calculateByWhileLoop(n))) 
    showOutput ("By non-tail recursion", measure(()=>calculateByRecursion(n))) 
    showOutput ("By tail recursion", measure(()=>calculateByTailRecursion(n))) 
    showOutput ("By tail recursion upward", measure(()=>calculateByTailRecursionUpward(n))) 
    } 
} 

sau đây là một số đầu ra từ SBT giao diện điều khiển (Trước «khi» thực hiện):

scala> example.Factorial.comparePerformance(10000) 
By loop in 3 ns 
By non-tail recursion in >>>>> StackOverflow!!!!!… see later!!! 
........ 

scala> example.Factorial.comparePerformance(1000) 
By loop in 3 ms 
By non-tail recursion in 1 ms 
By tail recursion in 4 ms 

scala> example.Factorial.comparePerformance(5000) 
By loop in 105 ms 
By non-tail recursion in 27 ms 
By tail recursion in 34 ms 

scala> example.Factorial.comparePerformance(10000) 
By loop in 236 ms 
By non-tail recursion in 106 ms  >>>> Now works!!! 
By tail recursion in 127 ms 

scala> example.Factorial.comparePerformance(20000) 
By loop in 977 ms 
By non-tail recursion in 495 ms 
By tail recursion in 564 ms 

scala> example.Factorial.comparePerformance(30000) 
By loop in 2285 ms 
By non-tail recursion in 1183 ms 
By tail recursion in 1281 ms 

Sau đây là một số đầu ra từ SBT giao diện điều khiển (Sau «khi» thực hiện):

scala> example.Factorial.comparePerformance(10000) 
By for loop in 252 ms 
By while loop in 246 ms 
By non-tail recursion in 130 ms 
By tail recursion in 136 ns 

scala> example.Factorial.comparePerformance(20000) 
By for loop in 984 ms 
By while loop in 1091 ms 
By non-tail recursion in 508 ms 
By tail recursion in 560 ms 

Sau đây là một số đầu ra từ SBT giao diện điều khiển (sau khi «trở lên» đuôi thực hiện đệ quy) thế giới đến trở lại lành mạnh:

scala> example.Factorial.comparePerformance(10000) 
By for loop in 259 ms 
By while loop in 229 ms 
By non-tail recursion in 114 ms 
By tail recursion in 119 ms 
By tail recursion upward in 105 ms 

scala> example.Factorial.comparePerformance(20000) 
By for loop in 1053 ms 
By while loop in 957 ms 
By non-tail recursion in 513 ms 
By tail recursion in 565 ms 
By tail recursion upward in 470 ms 

sau đây là một số đầu ra từ SBT console sau khi sửa bigint nhân trong «vòng»: thế giới là hoàn toàn sa ne:

scala> example.Factorial.comparePerformance(20000) 
By for loop in 498 ms 
By while loop in 502 ms 
By non-tail recursion in 521 ms 
By tail recursion in 611 ms 
By tail recursion upward in 503 ms 

bigint overhead và ngu ngốc thực hiện bởi tôi đeo mặt nạ hành vi mong đợi.

Thanks guys

PS .: Cuối cùng tôi nên lại tiêu đề bài này để «Một bài học lernt trên bigints»

+8

câu hỏi là gì? – Dan

+1

"Loops nên tránh" Đây là gây hiểu nhầm, cho vòng thường chậm hơn so với tương đương trong khi vòng trong scala. Đuôi đệ quy thường nhanh hơn đệ quy không đuôi, tôi sẽ đi ra ngoài một chi và nói nó chậm hơn vì sự đóng cửa mà bạn đang tạo ra trước khi hàm bắt đầu. –

+1

Ai có thể giúp tôi hiểu hành vi này chỉ cho tôi một tham chiếu có thẩm quyền. –

Trả lời

12

Đối với các vòng lặp không phải là thực sự khá vòng; chúng để hiểu về một phạm vi. Nếu bạn thực sự muốn một vòng lặp, bạn cần sử dụng while. (Trên thực tế, tôi nghĩ rằng các nhân BigInt ở đây là nặng đủ để nó không quan trọng.Nhưng bạn sẽ nhận thấy nếu bạn đang nhân Int s.)

Ngoài ra, bạn đã nhầm lẫn bản thân bằng cách sử dụng BigInt. Các BigInt của bạn càng lớn, nhân của bạn càng chậm. Vì vậy, vòng lặp không đuôi của bạn đếm lên trong khi vòng lặp đệ quy đuôi của bạn xuống có nghĩa là số thứ hai có số lượng lớn hơn để nhân lên.

Nếu bạn khắc phục hai vấn đề này, bạn sẽ thấy rằng sự tỉnh táo được khôi phục: vòng lặp và đuôi đệ quy có cùng tốc độ, với cả đệ quy thông thường và chậm hơn for. (Việc đệ quy thông thường có thể không chậm hơn nếu tối ưu hóa JVM tương đương)

(Ngoài ra, sửa lỗi tràn ngăn xếp có thể làm cho cuộc gọi tự động đệ quy, hoặc bỏ vòng lặp đủ xa để bạn không bị tràn quá lâu nữa.)

Cuối cùng, bạn sẽ nhận được kết quả kém trong một thời gian vì bạn đang nhân lên bên phải chứ không phải bên trái với số lượng nhỏ. Nó chỉ ra rằng BigInt của Java nhân nhanh hơn với số lượng nhỏ hơn ở bên trái.

+0

Tôi đang bối rối. Bằng cách nhìn vào mã tôi sẽ nói rằng cả hai đếm ngược. Có phải do sự đệ quy đuôi mà trật tự đã thay đổi bằng cách nào đó? – bluenote10

+0

Tôi vừa bổ sung «while» thực hiện tốt hơn như «for»… gimme 1 'để tích hợp perl của bạn! –

+1

@ bluenote10 - Việc đệ quy thông thường tính toán giá trị sâu nhất trước tiên. Đuôi đệ quy ngược lại, như được viết. Viết ra một vài thuật ngữ và bạn sẽ thấy nó. –

0

Scala phương pháp tĩnh cho factorial(n) (mã với 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) 
     } 
    } 
    } 

} 

kết quả Benchmark (vòng lặp nhanh hơn nhiều):

[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) 
Các vấn đề liên quan