2013-02-28 2 views
8

Я написал наивный тестовый слой для измерения производительности трех видов факториальной реализации: на основе петли, без хвостового рекурсивного и хвостового рекурсивного.Scala recursion vs loop: соображения производительности и времени выполнения

Удивительно мне худший производительным были те петли («в то время», как ожидается, будет более эффективным, поэтому я и при условии), которые стоят почти в два раза, чем хвост рекурсивной альтернативы.

ОТВЕТ: фиксируя выполнение цикла избежать * = оператор Wich опережать худший с BigInt из-за его внутренностей «петли» стал самым быстрым, как ожидалось

Другой «Вуду» поведение Я опытным было исключение StackOverflow , которое не было выбрано системно для одного и того же ввода в случае нерекурсивной реализации без хвоста. Я могу обойти StackOverlow путем постепенного вызова функции с большим и большим ценностей ... Я чувствую себя с ума :) Ответ: JVM требуется сходиться во время запуска, то поведение является когерентным и систематическое

Это код :

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

ниже некоторые выходы из SBT консоли (до «во время» реализация):

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 

Ниже некоторые выходы из SBT консоли (После того, как «в то время» реализация):

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 

Ниже некоторые выходы из SBT консоли (после того, как «вверх» реализации хвостовой рекурсии) мир пришел назад здравый:

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 

ниже некоторые выходы из SBT консоли после закрепления BigInt умножения в «петле»: мир полностью са пе:

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 над головой и глупы реализация мною маскируется ожидаемое поведение.

Спасибо, ребята

PS .: В конце концов, я должен вновь титул этот пост к «А lernt урок по BigInts»

+8

В чем вопрос? – Dan

+1

«Циклы следует избегать». Это вводит в заблуждение, for-loops, как правило, намного медленнее, чем эквивалентные, в то время как петли в scala. Рекурсия хвоста обычно быстрее, чем нерегулярная рекурсия, я собираюсь выйти на конечность и сказать, что она медленнее из-за закрытия, которое вы создаете до запуска функции. –

+1

Кто может помочь мне понять это поведение, указывая на авторитетную ссылку. –

ответ

12

Для петель на самом деле не совсем петель; они предназначены для понимания на диапазоне. Если вам действительно нужен цикл, вам нужно использовать while. (На самом деле, я думаю, что умножение здесь является достаточно тяжелым, так что это не имеет значения. Но вы заметите, если вы умножаете Int.)

Кроме того, вы смутили себя, используя BigInt. Чем больше ваш BigInt, тем медленнее умножение. Таким образом, ваша не-хвостовая петля подсчитывает до, в то время как ваши хвостовые петли цикла рекурсии down, что означает, что последний имеет более большие числа, чтобы размножаться.

Если вы исправите эти две проблемы, вы обнаружите, что здравомыслие восстанавливается: петли и рекурсия хвоста имеют одинаковую скорость, причем как регулярная рекурсия, так и for медленнее. (Регулярная рекурсия может быть не медленнее, если оптимизация JVM делает ее эквивалентной)

(Кроме того, исправление переполнения стека, вероятно, связано с тем, что JVM начинает встраивание и может либо сделать хвост-рекурсивный сам, либо развернуть цикл достаточно далеко так что вы больше не переполняете.)

И, наконец, вы получаете плохие результаты за и против, потому что вы умножаетесь справа, а не слева, с небольшим числом. Оказывается, BigInt Java умножается быстрее с меньшим числом слева.

+0

Я в замешательстве. Посмотрев на код, я бы сказал, что оба обратного отсчета. Из-за хвостовой рекурсии порядок каким-то образом изменился? – bluenote10

+0

Я только что добавил реализацию «while», которая превосходит как «для» ... gimme 1 'для интеграции вашего perl! –

+1

@ bluenote10 - Регулярная рекурсия сначала вычисляет самое глубокое значение. Рекурсия хвоста делает обратное, как написано. Выпишите несколько терминов, и вы это увидите. –

0

Scala статические методы factorial(n) (кодируются 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 
    } 
    } 

} 

характеристики:

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

контрольные показатели:

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

} 

Результаты тестов (цикл намного быстрее):

[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) 
Смежные вопросы