Software Design Blog

Journey of Rinat Abdullin

F# Has Better Performance Than C# in Math

As it turns out algorithms coded in F# code might have much better performance, compared to the ones in C# code.

Below you will find F# version of optimized sigma-comparison algorithm from the previous post.

Compared to the C#, this .NET code:

  • executes sigmoid1 benchmark in 588.8ms instead of 3899,2ms
  • executes sigmoid2 (LUT) benchmark in 156.6ms instead of 411.4ms

You’ll see below that this performance of F# is quite close to the performance of the similar implementation written in C.

Here’s the F# code:

#light

let Scale = 320.0f;
let Resolution = 2047;

let Min = -single(Resolution)/Scale;
let Max = single(Resolution)/Scale;

let range step a b =
  let count = int((b-a)/step);
  seq { for i in 0 .. count -> single(i)*step + a };

let lut = [| 
  for x in 0 .. Resolution ->
    single(1.0/(1.0 +  exp(-double(x)/double(Scale))))
  |]

let sigmoid1 value = 1.0f/(1.0f + exp(-value));

let sigmoid2 v = 
  if (v <= Min) then 0.0f;
  elif (v>= Max) then 1.0f;
  else
    let f = v * Scale;
    if (v>0.0f) then lut.[int (f + 0.5f)]
    else 1.0f - lut.[int(0.5f - f)];

let getError f = 
  let test = range 0.00001f -10.0f 10.0f;
  let errors = seq { 
    for v in test -> 
      abs(sigmoid1(single(v)) - f(single(v)))
  }
  Seq.max errors;

open System.Diagnostics;

let test f = 
  let sw = Stopwatch.StartNew(); 
  let mutable m = 0.0f;
  let result = 
    for t in 1 .. 10 do
      for x in 1 .. 1000000 do
        m <- f(single(x)/100000.0f-5.0f);
  sw.Elapsed.TotalMilliseconds;

printf "Max deviation is %f\n" (getError sigmoid2)
printf "10^7 iterations using sigmoid1: %f ms\n" (test sigmoid1)
printf "10^7 iterations using sigmoid2: %f ms\n" (test sigmoid2)

let c = System.Console.ReadKey(true);

And the output (Release compilation against F# 1.9.6.2 CTP with no debugger):

Max deviation is 0.001664
10^7 iterations using sigmoid1: 588.843700 ms
10^7 iterations using sigmoid2: 156.626700 ms

Chances are that the performance of sigmoid2 is better than that, but I just can’t figure out how to get rid of the mutable state.

Running as Mono

By the way, that’s how similar (although not exact) code works out under Mono (details):

$ gmcs test.cs && mono test.exe
Max detected error using LUT: 0.1568425 %
1000000 iterations using Sigmoid1() took 3591.858 ms
1000000 iterations using Sigmoid2() took 247.561 ms

Running as C

C is supposed to be really close to metal and highly efficient. Let’s check out how it works out for this algorithm.

Running C implementation of the code (developed by Henrik Gustafsson) yields following results:

Max deviation is 0.001664
10^7 iterations using sigmoid1: 628 ms
10^7 iterations using sigmoid2: 157 ms

Surprisingly enough, the performance is almost the same as F#. So F# must be doing really good job here.

Caveat: I’ve been using win32 compiler to get the binaries. 64 bit might improve the performance a bit. I’ll update the article if it does.

Summary

So it turns out that F# compiler creates code that is 2-6 times faster for the math code, compared to C# (while the output is still .NET compatible). And this F# code has performance close to the one of the C code.

All this makes me wonder, if it is worth switching to F# for the core NeuroEvolution code. Stay tuned for the series.