Search code examples
functionoptimizationf#record

Optimization of function value access in record using F#


Is there any reason why F# is not clever enough to optimize the following code? fast = 880 and slow = 8090.

type Data = { fn: int * int -> int }
let fn (x, y) = x + y
let data = { fn = fn }

let mutable a = 0
let s = System.Diagnostics.Stopwatch()

s.Start()
for i in 0 .. 1000000000 do
  a <- fn(i, i)
printfn "fast = %d" s.ElapsedMilliseconds

s.Restart()
for i in 0 .. 1000000000 do
  a <- data.fn(i, i)
printfn "slow = %d" s.ElapsedMilliseconds

Solution

  • Is there any reason why F# is not clever enough to optimize the following code?

    I would be surprised if F# compiler is able to optimize this case. In the end, fn is a record field which is to hold data, not to execute functions.

    Even on non-static members, the compiler can't inline them because these members are bounded by changing environment. By declaring a let binding, you have the advantage of static environment and the compiler is able to inline in some simple cases.

    Indeed in this example the fn function is inlined (adding inline doesn't change running time). The slow example is the price you pay for having a more powerful construct at hand.

    Whenever you have to create a record of functions, remember that interfaces and object expressions are better alternatives (less overheads, better intellisense):

    type Data2 =
        abstract fn : int * int -> int
    
    let data2 = 
        { new Data2 with
            member __.fn (x, y) = fn (x, y) }
    
    s.Restart()
    for i in 0 .. 1000000000 do
      a <- data2.fn(i, i)
    printfn "a bit slow = %d" s.ElapsedMilliseconds
    

    This is the result I got by executing in F# Interactive 64-bit:

    fast = 614
    slow = 7498
    a bit slow = 2765
    

    So the interface-based approach is 3x faster than record-based approach and 3x slower than the inline method.