Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CLR vs OCaml exception overhead

Reading Beginning F# - Robert Pickering I focused on the following paragraph:

Programmers coming from an OCaml background should be careful when using exceptions in F#. Because of the architecture of the CLR, throwing an exception is pretty expensive—quite a bit more expensive than in OCaml. If you throw a lot of exceptions, profile your code carefully to decide whether the performance costs are worth it. If the costs are too high, revise the code appropriately.

Why, because of CLR, throwing an exception is more expensive if F# than in OCaml? And what is the best way to revise the code appropriately in this case?

like image 678
gliderkite Avatar asked Jun 09 '12 21:06

gliderkite


People also ask

Does OCaml have exceptions?

OCaml has exception handling to deal with exceptions, application-level errors, out-of-band data and the like. Exceptions are a very dynamic construct, but there is a third-party application that performs a static analysis of your use of exceptions and can find inconsistencies and errors at compile time!

What is Failwith in OCaml?

The simplest one is failwith , which could be defined as follows: let failwith msg = raise (Failure msg);; val failwith : string -> 'a = <fun> OCaml. There are several other useful functions for raising exceptions, which can be found in the API documentation for the Common and Exn modules in Base.

How do you catch errors in OCaml?

One way of handling errors in OCaml is exceptions. The standard library relies heavily upon them. Exceptions belong to the type exn (an extensible sum type): exception Foo of string let i_will_fail () = raise (Foo "Oh no!")


3 Answers

Exceptions in the CLR are very rich, and provide a lot of detail. Rico Mariani posted an (old, but still relevant) post on the cost of exceptions in the CLR which details some of this.

As such, throwing an exception in the CLR has a higher relative cost than in some other environments, including OCaml.

And what is the best way to revise the code appropriately in this case?

If you expect the exception to be raised in normal, non-exceptional circumstances, you can rethink your algorithm and API in a way that avoids the exception entirely. Try to provide an alternative API where you can test for the circumstance prior to causing the exception to be raised, for example.

like image 86
Reed Copsey Avatar answered Oct 12 '22 22:10

Reed Copsey


Reed already explained why .NET exceptions behave differently than OCaml exceptions. In general, .NET exceptions are suitable only for exceptional situations and are designed for that purpose. OCaml has more lightweight model and so they are used to also implement some control flow patterns.

To give a concrete example, in OCaml you could use exception to implement breaking of a loop. For example, say you have a function test that tests whether a number is a number we want. The following iterates over numbers from 1 to 100 and returns first matching number:

// Simple exception used to return the result
exception Returned of int

try
  // Iterate over numbers and throw if we find matching number
  for n in 0 .. 100 do
    printfn "Testing: %d" n
    if test n then raise (Returned n)
  -1                 // Return -1 if not found
with Returned r -> r // Return the result here

To implement this without exceptions, you have two options. You could write a recursive function that has the same behaviour - if you call find 0 (and it is compiled to essentially the same IL code as using return n inside for loop in C#):

let rec find n = 
  printfn "Testing: %d" n
  if n > 100 then -1  // Return -1 if not found
  elif test n then n  // Return the first found result 
  else find (n + 1)   // Continue iterating

The encoding using recursive functions can be a bit lengthly, but you can also use standard functions provided by the F# library. This is often the best way to rewrite code that would use OCaml exceptions for control flow. In this case, you can write:

// Find the first value matching the 'test' predicate
let res = seq { 0 .. 100 } |> Seq.tryFind test
// This returns an option type which is 'None' if the value 
// was not found and 'Some(x)' if the value was found.
// You can use pattern matching to return '-1' in the default case:
match res with
| None -> -1
| Some n -> n

If you're not familiar with option types, then take a look at some introductory material. F# wikibook has a good tutorial and MSDN documentation has useful examples too.

Using an appropriate function from the Seq module often makes the code a lot shorter, so it is preferrable. It may be slightly less efficient than using recursion directly, but in most of the cases, you don't need to worry about that.

EDIT: I was curious about the actual performance. The version using Seq.tryFind is more efficient if the input is lazily generated sequence seq { 1 .. 100 } instead of a list [ 1 .. 100 ] (because of the costs of list allocation). With these changes, and test function that returns 25th element, the time needed to run the code 100000 times on my machine is:

exceptions   2.400sec  
recursion    0.013sec  
Seq.tryFind  0.240sec

This is extremely trivial sample, so I think the solution using Seq will not generally run 10 times slower than equivalent code written using recursion. The slowdown is probably due to the allocation of additional data structures (object representing the sequence, closures, ...) and also due to additional indirection (the code needs numerous virtual method calls, instead of just plain numeric operations and jumps). However, exceptions are even more costly and don't make the code shorter or more readable in any way...

like image 22
Tomas Petricek Avatar answered Oct 12 '22 23:10

Tomas Petricek


Why, because of CLR, throwing an exception is more expensive if F# than in OCaml?

OCaml was heavily optimized for the use of exceptions as control flow. In contrast, exceptions in .NET have not been optimized at all.

Note that the performance difference is huge. Exceptions are around 600× faster in OCaml than they are in F#. Even C++ is around 6× slower than OCaml in this regard, according to my benchmarks.

Even though .NET exceptions allegedly provide more (OCaml provides stack traces, what more do you want?) I cannot think of any reason why they should be as slow as they are.

And what is the best way to revise the code appropriately in this case?

In F#, you should write "total" functions. This means your function should return a value of a union type indicating the kind of result, e.g. normal or exceptional.

In particular, calls to find should be replaced with calls to tryFind that return a value of the option type that is either Some value or None if the key element was not present in the collection.

like image 44
J D Avatar answered Oct 12 '22 22:10

J D