Thank you for your answers.
I am actually in the case of a webserver where I need to avoid interrupting it, even in 'exceptional' cases, but rather report the problem in a log, and return a default value whenever it is possible. Options seem to be the best choice, not only from a safety point of view but also performance-wise.

Cheers

On Thu, Apr 5, 2012 at 5:05 AM, Goswin von Brederlow <goswin-v-b@web.de> wrote:
Pierre Chopin <pierre@punchup.com> writes:

> Hi,
>
> I benchmarked two programs, in one case the main function throw an exception
> that is caught, in the other the function returns an option that is pattern
> matched on.
>
> I noticed that, whether the exception is thrown or not, the option version is
> always faster.
>
> Is there any case where it makes sense, performance wise, to use exception
> instead of 'a option ?

I find that in most cases speed is not a major concern and then it comes
down to taste and readability of the code.

> test1.ml
> ----------------------------------------------------------------------
>
> exception Foo
> let f x =
>  if x =1 then raise Foo else ()
>
> ;;
>
>  for i = 0 to 10_000_000 do
> try
>     f 1
> with Foo -> ()
> done

0.34s user 0.01s system 99% cpu 0.351 total

That is rather short for a test. lets add 2 zeroes to the loop. And lets
call f 0 and f 1 to test both cases:

f 0: 17.72s user 0.02s system 99% cpu 17.792 total
f 1: 35.30s user 0.02s system 99% cpu 35.371 total

> ------------------------------------------------------------------------
> test2.ml:
> ------------------------------------------------------------------------
> let f x =
>     if x=1 then None else Some ()
>
> ;;
> for i = 0 to 10_000_000 do
>     match f 1 with
>         None -> ()
>     |   Some s -> s
>     done
> ------------------------------------------------------------------------

f 0: 11.60s user 0.02s system 99% cpu 11.655 total
f 1: 10.91s user 0.01s system 99% cpu 10.933 total

And lets test the speed when the exception is actualy exceptional:

exception Foo
let f x = if x =1 then raise Foo else ()

let () =
 try
   for i = 0 to 1000000000 do
     f 0
   done
 with Foo -> ()

9.94s user 0.00s system 99% cpu 9.946 total

Someone said in deep recursions exceptions are faster because they don't
have to unwind the stack:

exception Result of int

let rec fac acc = function
 | 1 -> raise (Result acc)
 | n -> fac (n * acc) (n - 1)

let () =
 for i = 0 to 100_000_000 do
   try
     fac 1 50
   with Result _ -> ()
 done

71.88s user 0.00s system 99% cpu 1:11.90 total


let rec fac acc = function
 | 1 -> acc
 | n -> fac (n * acc) (n - 1)

let () =
 for i = 0 to 100_000_000 do
   ignore (fac 1 50)
 done

67.04s user 0.02s system 99% cpu 1:07.08 total


Not feeling it. Lets try something not tail recursive:

exception Error

let rec foo = function
 | 1 -> raise Error
 | n -> n * (foo (n - 1))

let () =
 for i = 0 to 100_000_000 do
   try
     ignore (foo 50)
   with Error -> ()
 done

25.03s user 0.01s system 99% cpu 25.068 total

let rec foo = function
 | 1 -> None
 | n -> match foo (n - 1) with None -> None | Some x -> Some (n * x)

let () =
 for i = 0 to 100_000_000 do
   ignore (foo 50)
 done

49.48s user 0.01s system 99% cpu 49.508 total


In conclusion I would have to say that exceptions are better if they are
exceptional.

When you do not catch them right away or not at all then they are
better. The "try" command is more expensive than an option type and if
you are going to catch the exception right away anyway then options are
faster.

But if you don't catch them right away the cost of the try can be
amortized over many calls and becomes cheaper. Or if you don't catch the
exception at all then you can get a nice backtrace of where the
exception occured.

If your code is not tail recursive then option types mean you have to
match them on every level again and again and allocate a ton of 'Some x'
if no error occurs. You can make your code tail recursive or use
exception to improve performance there.



If you are writing a module then consider providing both flavours for
functions, one with exceptions and one with options. Even if you only do
something like this:

let find x y = ....

let raise_on_none exn f arg =
 match f arg with
 | None -> raise exn
 | Some x -> x

let find_exn x y = raise_on_none Not_found (find x) y

Obviously option only works for exceptions like Not_found. If you want
to return an error you might have to use something like

  type ('a, 'b) result = Result of 'a | Error of 'b

Putting the 2 flavours into different submodules can keep things tidy too.

MfG
       Goswin



--
Pierre Chopin,
Chief Technology Officer and co-founder
punchup LLC
pierre@punchup.com