Here, I created a new benchmark (using Thyme):
import scala.util.control.ControlThrowable
val e = new Exception
trait T {
def foo: Int
def bar: Int
def baz: Int
def qux: Either[Throwable, Int]
def yig: Either[Exception, Int]
}
class A extends T {
def foo: Int = throw new Exception
def bar: Int = throw e
def baz: Int = throw new ControlThrowable {}
def qux: Either[Throwable, Int] = Left(new ControlThrowable {})
def yig: Either[Exception, Int] = Left(new Exception)
}
class B extends T {
def foo: Int = throw new Exception
def bar: Int = throw e
def baz: Int = throw new ControlThrowable {}
def qux: Either[Throwable, Int] = Left(new ControlThrowable {})
def yig: Either[Exception, Int] = Left(new Exception)
}
class C extends T {
def foo: Int = throw new Exception
def bar: Int = throw e
def baz: Int = throw new ControlThrowable {}
def qux: Either[Throwable, Int] = Left(new ControlThrowable {})
def yig: Either[Exception, Int] = Left(new Exception)
}
class D extends T {
def foo: Int = throw new Exception
def bar: Int = throw e
def baz: Int = throw new ControlThrowable {}
def qux: Either[Throwable, Int] = Left(new ControlThrowable {})
def yig: Either[Exception, Int] = Left(new Exception)
}
val a = Array.tabulate(1000)(i => (i%4) match { case 0 => new A; case 1 => new B; case 2 => new C; case _ => new D })
val th = new ichi.bench.Thyme
def foo = {
var i, s = 0
while (i < a.length) {
try { s += a(i).foo } catch { case _: Exception => s += 1 }
i += 1
}
s
}
def bar = {
var i, s = 0
while (i < a.length) {
try { s += a(i).bar } catch { case _: Exception => s += 1 }
i += 1
}
s
}
def baz = {
var i, s = 0
while (i < a.length) {
try { s += a(i).baz } catch { case _: Throwable => s += 1 }
i += 1
}
s
}
def qux = {
var i, s = 0
while (i < a.length) {
a(i).qux match {
case Right(i) => s += i
case _ => s += 1
}
i += 1
}
s
}
def yig = {
var i, s = 0
while (i < a.length) {
a(i).yig match {
case Right(i) => s += i
case _ => s += 1
}
i += 1
}
s
}
th.pbenchOff(){ foo }{ qux }
th.pbenchOff(){ bar }{ qux }
th.pbenchOff(){ baz }{ qux }
th.pbenchOff(){ yig }{ qux }
Results (one run, but results are typical):
Benchmark comparison (in 617.3 ms)
Significantly different (p ~= 0)
Time ratio: 0.00577 95% CI 0.00553 - 0.00601 (n=20)
First 1.910 ms 95% CI 1.864 ms - 1.957 ms
Second 11.03 us 95% CI 10.66 us - 11.40 us
Benchmark comparison (in 609.1 ms)
Significantly different (p ~= 0)
Time ratio: 0.10376 95% CI 0.10127 - 0.10626 (n=20)
First 107.6 us 95% CI 106.0 us - 109.3 us
Second 11.17 us 95% CI 10.96 us - 11.37 us
Benchmark comparison (in 539.0 ms)
Significantly different (p ~= 0)
Time ratio: 0.13197 95% CI 0.13019 - 0.13375 (n=20)
First 81.38 us 95% CI 80.58 us - 82.18 us
Second 10.74 us 95% CI 10.64 us - 10.84 us
Individual benchmarks not fully consistent with head-to-head (p ~= 1.211e-12)
First 91.14 us 95% CI 90.31 us - 91.98 us
Second 10.52 us 95% CI 10.45 us - 10.59 us
Benchmark comparison (in 1.826 s)
Significantly different (p ~= 0)
Time ratio: 0.00582 95% CI 0.00578 - 0.00587 (n=30)
First 1.840 ms 95% CI 1.832 ms - 1.849 ms
Second 10.71 us 95% CI 10.65 us - 10.78 us
Individual benchmarks not fully consistent with head-to-head (p ~= 5.094e-07)
First 2.106 ms 95% CI 2.082 ms - 2.131 ms
Second 10.55 us 95% CI 10.49 us - 10.60 us
Conclusion: passing back a new stackless exception wrapped in a Left
(I don’t know why you’d do this…) is about 8x faster than throwing that stackless exception, about 10x faster than throwing a pre-generated stack-containing exception, and about 170x faster than passing back a new exception with a stack or creating and throwing that exception.
Head-to-head of the pre-generated vs. freshly created exception, both with throwing, gives about a 20x advantage to pre-generation:
scala> th.pbenchOff(){ foo }{ bar }
Benchmark comparison (in 506.0 ms)
Significantly different (p ~= 0)
Time ratio: 0.04942 95% CI 0.04867 - 0.05016 (n=20)
First 1.594 ms 95% CI 1.579 ms - 1.609 ms
Second 78.79 us 95% CI 77.86 us - 79.72 us
Individual benchmarks not fully consistent with head-to-head (p ~= 8.028e-06)
First 1.857 ms 95% CI 1.781 ms - 1.933 ms
Second 78.38 us 95% CI 77.89 us - 78.88 us
Quick and dirty and various things could go wrong (and did during testing–sometimes I got weird unoptimized results), but this is roughly what I’ve seen before and is a good rule of thumb.
- Return value without boxing–super fast!! (not shown here)
- Return value boxed–fast!
- Throw stackless or pre-generated exception–10x slower
- Create stacked exception–20x slower again