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