Sunday, 22 November 2009

Experimenting with F#

The F# language has been around for a couple of years now but has been evolving gradually from a research project to a production-quality language. It already has some industry adoption, with projects in a few large banks being built using the language. With its inclusion in Visual Studio 2010 I expect that we’re going to see it being more widely used in the next few years. At first sight it seems a pleasant language to work with, but it’s been years since I’ve written even a moderately sized application in a functional language so it will be interesting to try.

I’m going to start out fairly slowly, to get a feel for how the code I write gets transformed into CIL, to understand how it’s implemented and so reassure myself that I’m not writing F# in a style that is going to compile down to badly performing IL.

In the first couple of posts I’m going to put together a simple option pricer using the closed-form Black-Scholes approach. This formula requires the standard Normal distribution’s PDF and CDF which, as a learning exercise, I’ll implement below.

For reference, I will be using the October 2009 CTP (1.9.7.8).

The Normal Probability Density function

The Standard Normal probability density function is:
So a straightfoward implementation, factoring out the constant portion of the formula, is:
namespace Distributions

open System

module Normal = 
    /// Internal constant used in the evaluation of the PDF. 
    let private OneOverSqrt2PI = 1.0/Math.Sqrt(2.0 * Math.PI)

    /// Evaluates the standard normal PDF at point x. 
    let n x = Math.Exp( -0.5*x*x ) * OneOverSqrt2PI
Firing up Reflector we see the following IL for the Normal.n method:
.method public static float64 n(float64 x) cil managed
{
    .maxstack 4
    L_0000: nop 
    L_0001: ldc.r8 -0.5
    L_000a: ldarg.0 
    L_000b: mul 
    L_000c: ldarg.0 
    L_000d: mul 
    L_000e: call float64 [mscorlib]System.Math::Exp(float64)
    L_0013: call float64 Distributions.Normal::get_OneOverSqrt2PI()
    L_0018: mul 
    L_0019: ret 
}

That was a little surprising: I had assumed that the private OneOverSqrt2PI would have been translated into a private static double in the class implementing the module. Instead it had been implemented as a private property, with the constant 1/sqrt(2*PI) being retrieved by a call to the property’s getter.

A further surprise was that the double field backing the OneOverSqrt2PI property and the .cctor initialising it were not contained within the class implementing the module: they were placed in a private class named StartupCode$LibraryName.$Distributions (after the namespace). The property's getter retrieved the calculated value from this class. It seems that the F# compiler systematically generates a "startup class" for each namespace within the application.

Going one step further, it’s interesting to see what the JIT does with this: will the method retrieving the constant be inlined, or will we incur the cost of an extra method call each time we evaluate the PDF?

By default Visual Studio will suppress the JIT optimisations when the code is being debugged, which would disable the inlining even if the JIT would normally inline the method. To run with optimisation enabled, it is first necessary to go to Tools -> Options -> debugging and untick Suppress JIT optimization on module load and Enable just my code.

Having done that and hit a breakpoint at Normal.n, it's possibly to get a nice disassembly of the optimized JIT'd code using SOS.dll. To do this type .load sos in the immediate debug window, then use !u to disassemble the code at the instruction pointer EIP (copied from the registers debug window):

push  ebp
mov   ebp,esp
sub   esp,8
fld   qword ptr [ebp+8]
fld   st(0)
fmul  dword ptr ds:[01519424h]
fmulp st(1),st
sub   esp,8
fstp  qword ptr [esp]
call  675D746F (System.Math.Exp(Double))
fstp  qword ptr [ebp-8]
call  dword ptr ds:[00143828h] (Distributions.Normal.get_OneOverSqrt2PI())
fld   qword ptr [ebp-8]
fmulp st(1),st
mov   esp,ebp
pop   ebp
ret   8

In the disassembly above, it’s can be seen that the retrieval of the constant OneOverSqrt2PI is not inlined. This will still run pretty quickly, but is there any way we can improve on this?

The Literal attribute?

Being an F# novice, the first possible solution that jumped out at me when skimming the specification was the [<Literal>] attribute. The documentation states that a let binding annotated with [<Literal>] would be treated as if it were a literal at the point of use. Trying it out, when the let is annotated with [<Literal>] its value is inlined in the generated IL. Unfortunately to use literal the value must be a compile-time constant. Changing the F# source to:

namespace Distributions

open System

module Normal = 
    /// Internal constant used in the evaluation of the PDF: equal to 
    /// 1.0/sqrt(2 * PI)
    [<Literal>]
    let private OneOverSqrt2PI = 0.398942280401433

    /// Evaluates the standard normal PDF at point x. 
    let n x = Math.Exp( -0.5*x*x ) * OneOverSqrt2PI
results in the following IL, where it can be seen that the constant has been inlined at compile time:
.method public static float64 n(float64 x) cil managed
{
    .maxstack 4
    L_0000: nop 
    L_0001: ldc.r8 -0.5
    L_000a: ldarg.0 
    L_000b: mul 
    L_000c: ldarg.0 
    L_000d: mul 
    L_000e: call float64 [mscorlib]System.Math::Exp(float64)
    L_0013: ldc.r8 0.398942280401433
    L_001c: mul 
    L_001d: ret 
}

However, when the value of the let binding is a compile-time constant, then it it inlined even without the [<Literal>] attribute. Reading Chris Smith's article, F# Zen - The Literal Attribute, it appears that the purpose of the literal attribute is to cause the binding to be interpreted as its value in constructs where it would otherwise be considered to be a new local binding of the same name - for example, to use it as a pattern to match against, not a name to bind the value to.

Wrapping up...

I had expected to cover both the PDF and the CDF in a single post, but having spent this many column-inches on just the simple PDF I'll save looking at the CDF for another post.

This post leaves me with a few questions still open:

  • Why are the Module-level constant values are held in a per-namespace StartupCode class? (scope?)
  • Is there an alternative to using a compile-time constant that avoids a non-inlined method call?
  • Why get_OneOverSqrt2PI wasn't inlined - it would seem a good candidate given it simply returns a readonly field
I'm sure some of the answers will become apparent when I have the time to finish digesting the F# specification, but if anyone reading this post knows the answer to any of the above I'd be interested to know.

No comments:

Post a Comment