[MLton-user] Performance issue with -const 'Exn.keepHistory true'

Matthew Fluet fluet at cs.cornell.edu
Sat Nov 18 20:43:03 PST 2006


> We used to compile our code with MLton 20030716p1 with the option
> '-exn-history true'.
>
> It was very useful when we had to solve some minor robustness issues as it
> was not too costly (we could use it on production code) and it gave us the
> exact location of the exception and "handle" clauses.

What slowdown is considered "not too costly"?

> I know the option (now -const 'Exn.keepHistory true') has been improved in
> order to print a full call stack but the consequence is that it is now too
> costly to be used in production code when compiling using MLton 20050712.

Correspondingly, what slowdown is considered "too costly"?

> Is there a way to get the old behaviour using MLton 2005 ?

Unfortunately, no.  The infrastructure that supported the old-style 
exception history was removed.

The log for the commit that changed over to the new exception history 
style includes the following note:

   Used MLton.CallStack to improve MLton.Exn.history.  Exn.keepHistory
   true now implies -profile call.  The first time an exception is
   raised, we call MLton.CallStack.current and store the result in the
   raised exception.  Exn.history simply extracts the call stack and
   converts it to a string list.  The output of this is a vast
   improvement over what was there before.  However, there is a cost --
   raising an exception now takes time proportional to the size of the
   call stack instead of constant time.  Of course, this is only with
   Exn.keepHistory true, which will probably only be used for debugging.
   I can imagine trickier implementations that have better performance,
   only walking the stack up to the handler, and walking the rest if the
   exception is later re-raised.  A user could also roll their own call
   stack stuff (say, for Assert.assert), where the program is compiled
   with -profile call, but with Exn.keepHistory false, and only grabs the
   call stack when there is an exception that is really an error, as
   opposed to an exception used for some other control flow.  Another
   approach would be for us to provide special raise functions that {do,
   do not} grab the history.

I'll highlight three things:
  1) The cost proportional to the call stack is incurred whether or not the
     exception is handled and whether or not the exception's history is
     taken.  Hence, if one uses exceptions as a control-flow mechanism,
     then they can be fairly costly.
  2) The old exception history wasn't quite constant time.  It was time
     proportional to the (dynamic) number of exception handlers between
     the exception's raise point and the exception's handle point.  In the
     worst case, this would itself be proporational to the size of the call
     stack, but, in practice, the number of dynamic handlers is rarely very
     high.  Furthermore, if one is using exceptions as a control-flow
     mechanism, then the number of dynamic handlers is likely to be very
     small (for that raised exception).
  3) The fact that 'Exn.keepHistory true' implies '-profile call' adds some
     additional overhead to the program.  This is because it instructs the
     compiler to maintain source code position information used by the
     various kinds of profiling.  It is meant to be as lightweight as
     possible; with '-profile call', the source information does not
     translate into any runtime operations (it may with the other kinds of
     profiling).  However, the simple presence of the source code
     information in the intermediate code can affect some optimizations, so
     one won't necessarily have exactly the program.

I would suggest the following:
  1) Compile your program with  -profile call  (and without -const
     'Exn.keepHistory true').

     a) If this program doesn't have acceptable performance, then there may
        be an optimization that is missed in the presence of the profiling
        information.  We would be interested in figuring out why the
        optimization is being missed and improve the compiler to catch it.
        This could improve the performance of 'Exn.keepHistory true' so
        that you could use it on production code.

     b) If this program does have acceptable performance, then it would
        appear that your program is raising many exceptions (with large
        call stacks), but handling then without reporting the history.

        In this case, as mentioned in the note above, you could roll your
        own call stack stuff and only capture the call stack at points
        where you think you might be interested in the failure.  Of course,
        this only really helps if the robustness issues you are interested
        in correspond to raises of particular exceptions that you can
        statically find in your codebase. (i.e., you are interested in
        exceptions of the form "Assert of string", but you don't care about
        "Overflow" or "Subscript" exceptions.

        In this case, we could also consider other ways of making exception
        history more efficient.  For example, in addition to the ideas in
        the note above, we could acheive constant time raises by only
        taking a fixed number of stack frames when raising an exception.  I
        could imagine that only taking 5 frames would often give you
        sufficient context to serve debugging purposes.

Hope this helps.




More information about the MLton-user mailing list