Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Speed wars: OCaml vs. F#

391 views
Skip to first unread message

w_a_...@yahoo.com

unread,
Feb 12, 2009, 12:41:37 PM2/12/09
to
OCaml: 5.921 seconds
F#: 2.641 seconds

For this task F# is 2.24 times as fast.

OCaml -----------------------

let runs = 100
let max_iterations = 1000

let iterate ci cr =
let bailout = 4.0 in
let rec loop zi zr i =
if i > max_iterations then
0
else
let temp = zr *. zi and
zr2 = zr *. zr and
zi2 = zi *. zi in
if zi2 +. zr2 > bailout then
i
else
loop (temp +. temp +. ci) (zr2 -. zi2 +. cr) (i + 1)
in
loop 0.0 0.0 1

let mandelbrot n =
for y = -39 to 38 do
if 1 = n then print_endline "";
for x = -39 to 38 do
let i = iterate
(float x /. 40.0) (float y /. 40.0 -. 0.5) in
if 1 = n then
print_string ( if 0 = i then "*" else " " );
done
done;;

let start_time = Sys.time () in
for iter = 1 to runs do
mandelbrot iter
done;
print_endline "";
print_float ( Sys.time () -. start_time );
print_endline "";


F# ----------------------------

open System

let runs = 100
let max_iterations = 1000

let iterate ci cr =
let bailout = 4.0 in
let rec loop zi zr i =
if i > max_iterations then
0
else
let temp = zr * zi and
zr2 = zr * zr and
zi2 = zi * zi in
if zi2 + zr2 > bailout then
i
else
loop (temp + temp + ci) (zr2 - zi2 + cr) (i + 1)
in
loop 0.0 0.0 1

let mandelbrot n =
for y = -39 to 38 do
if 1 = n then Console.WriteLine "";
for x = -39 to 38 do
let i = iterate
(float x / 40.0) (float y / 40.0 - 0.5) in
if 1 = n then
Console.Write ( if 0 = i then "*" else " " );
done
done;;

let start_time = Sys.time () in
for i = 1 to runs do
mandelbrot i
done;
Console.WriteLine "";
Console.WriteLine( Sys.time () - start_time );

mehdide...@gmail.com

unread,
Feb 12, 2009, 5:22:22 PM2/12/09
to
On Feb 12, 8:41 pm, w_a_x_...@yahoo.com wrote:
> OCaml: 5.921 seconds
> F#:    2.641 seconds
Wow
What is your system's specifications ? (OS, compiler version, RAM,
CPU, ...)
On my system it took 73 seconds to run it on OCaml
(I saved your code in an .ML file and opened it with OCamlwin.exe)
My system: Vista SP1 , 3 GB RAM , 1.8 GHZ (2 cores) and I'm using
OCaml 3.10.2 for Windows
The F# code is not correct (Sys.time does not exist in .NET, you
should use DateTime and TimeSpan. (I'm using FSharp 1.9.6.2, the CTP)

William James

unread,
Feb 12, 2009, 10:33:18 PM2/12/09
to
mehdide...@gmail.com wrote:

> On Feb 12, 8:41 pm, w_a_x_...@yahoo.com wrote:
> > OCaml: 5.921 seconds
> > F#:    2.641 seconds
> Wow
> What is your system's specifications ? (OS, compiler version, RAM,
> CPU, ...)
> On my system it took 73 seconds to run it on OCaml
> (I saved your code in an .ML file and opened it with OCamlwin.exe)

I compiled it with ocamlopt.
windows, Objective Caml version 3.11.0 (mingw), 3.2GHz Pentium

> My system: Vista SP1 , 3 GB RAM , 1.8 GHZ (2 cores) and I'm using
> OCaml 3.10.2 for Windows
> The F# code is not correct (Sys.time does not exist in .NET, you
> should use DateTime and TimeSpan. (I'm using FSharp 1.9.6.2, the CTP)

Correct or not, it works. I guess Sys.time is provided for OCaml
compatibility. How do you use TimeSpan?
Did you run the F# program?

William James

unread,
Feb 12, 2009, 11:11:31 PM2/12/09
to
William James wrote:

> How do you use TimeSpan?


let t1 = System.DateTime.Now

// Do some work.

let t2 = System.DateTime.Now

System.Console.WriteLine( (t2 - t1).TotalSeconds );

Jon Harrop

unread,
Feb 13, 2009, 3:14:31 PM2/13/09
to
mehdide...@gmail.com wrote:
> On Feb 12, 8:41 pm, w_a_x_...@yahoo.com wrote:
>> OCaml: 5.921 seconds
>> F#:    2.641 seconds
> Wow
> What is your system's specifications ? (OS, compiler version, RAM,
> CPU, ...)
> On my system it took 73 seconds to run it on OCaml
> (I saved your code in an .ML file and opened it with OCamlwin.exe)

You are running an interpreter. You need to compile the OCaml to get decent
performance, of course.

> My system: Vista SP1 , 3 GB RAM , 1.8 GHZ (2 cores) and I'm using
> OCaml 3.10.2 for Windows
> The F# code is not correct (Sys.time does not exist in .NET,

No. Sys.time is provided by the F# Powerpack DLL.

> you should use DateTime and TimeSpan. (I'm using FSharp 1.9.6.2, the CTP)

No. OCaml's Sys.time measures elapsed CPU time whereas DateTime measures
wall clock time.

--
Dr Jon D Harrop, Flying Frog Consultancy Ltd.
http://www.ffconsultancy.com/?u

Jon Harrop

unread,
Feb 13, 2009, 3:30:27 PM2/13/09
to
w_a_...@yahoo.com wrote:
> OCaml: 5.921 seconds
> F#: 2.641 seconds
>
> For this task F# is 2.24 times as fast.

That is quite common for numerical tasks because OCaml's lack of JIT
compilation forces it to use a uniform representation of values in general.

> OCaml -----------------------
>
> let runs = 100
> let max_iterations = 1000
>
> let iterate ci cr =
> let bailout = 4.0 in
> let rec loop zi zr i =
> if i > max_iterations then
> 0
> else
> let temp = zr *. zi and
> zr2 = zr *. zr and
> zi2 = zi *. zi in
> if zi2 +. zr2 > bailout then
> i
> else
> loop (temp +. temp +. ci) (zr2 -. zi2 +. cr) (i + 1)
> in
> loop 0.0 0.0 1

In this case, your use of a recursive function rather than a loop is
probably leading OCaml to needlessly box and immediately unbox the function
arguments. Using a loop instead should improve performance significantly.

On the other hand, what you are really doing is complex-valued arithmetic
and, consequently, it would be interesting to compare the performance of
idiomatic code that uses the complex number representations provided with
OCaml and F#. You will find that F# runs circles around OCaml there (e.g.
FFT over complexes is 5.5x faster in F# than OCaml) because OCaml cannot
support structs efficiently because it lacks JIT compilation (or whole
program optimization like MLton) so it is forced to box all complex
numbers, which is hugely inefficient in numerical routines.

Furthermore, algorithms like this one can benefit enormously from
parallelism on multicore machines. F# makes that easy whereas OCaml makes
it hard or impossible.

mehdide...@gmail.com

unread,
Feb 13, 2009, 4:03:03 PM2/13/09
to
On Feb 13, 11:14 pm, Jon Harrop <j...@ffconsultancy.com> wrote:
> No. Sys.time is provided by the F# Powerpack DLL.
So, what's the best practice ? Using F#'s libraries or BCL ? (I mean,
is there any chance that using F#'s libraries become obsolete in
future ?)

> No. OCaml's Sys.time measures elapsed CPU time whereas DateTime measures
> wall clock time.
>

Thank you

Jon Harrop

unread,
Feb 13, 2009, 4:50:23 PM2/13/09
to
mehdide...@gmail.com wrote:
> On Feb 13, 11:14 pm, Jon Harrop <j...@ffconsultancy.com> wrote:
> > No. Sys.time is provided by the F# Powerpack DLL.
>
> So, what's the best practice ? Using F#'s libraries or BCL ? (I mean,
> is there any chance that using F#'s libraries become obsolete in
> future ?)

There is no real "best practice" to speak of because F# is a (fast!) moving
target. For example, they have deprecated epsilon_float but provided no
alternative. So I'd say go with whatever works today and that is exactly
what you did. If they do ever remove things like Sys.time then you can
still cut and paste their old code just as I do with epsilon_float. You
haven't really lost out...

Jon Harrop

unread,
Feb 13, 2009, 4:54:07 PM2/13/09
to
William James wrote:
> William James wrote:
>> How do you use TimeSpan?
>
>
> let t1 = System.DateTime.Now
>
> // Do some work.
>
> let t2 = System.DateTime.Now

Use System.Diagnostics.Stopwatch.StartNew() to measure elapsed real time
on .NET.

> System.Console.WriteLine( (t2 - t1).TotalSeconds );

Use F#'s printf instead of System.Console.Writeline:

printfn "Took %dms" t.ElapsedMilliseconds

William James

unread,
Feb 13, 2009, 11:41:39 PM2/13/09
to
Jon Harrop wrote:

> On the other hand, what you are really doing is complex-valued
> arithmetic and, consequently, it would be interesting to compare the
> performance of idiomatic code that uses the complex number
> representations provided with OCaml and F#. You will find that F#
> runs circles around OCaml there (e.g. FFT over complexes is 5.5x
> faster in F# than OCaml) because OCaml cannot support structs
> efficiently because it lacks JIT compilation (or whole program
> optimization like MLton) so it is forced to box all complex numbers,
> which is hugely inefficient in numerical routines.


F# times on my 2 GHz laptop:

2.0629664 using ordinary floats
18.206179 using Math.Complex


The F# code:

open Math.Complex

let runs = 100
let max_iterations = 1000

let iterate cmp =
let rec loop (z:Math.complex) i =


if i > max_iterations then
0
else

// Using magnitude is too slow.
// if magnitude z >= 2.0 then
if (z.r * z.r + z.i * z.i) >= 4.0 then
i
else
loop (z * z + cmp) (i + 1)
in
loop zero 1

let mandelbrot n =
for y = -39 to 38 do

if 1 = n then printfn "";


for x = -39 to 38 do
let i = iterate

(complex ((float y / 40.0) - 0.5) (float x / 40.0)) in
if 1 = n then printf "%s" ( if 0 = i then "*" else " " )
done
done;;

let start_time = Sys.time () in

for i = 1 to runs do
mandelbrot i
done;
printfn "\n%f" (Sys.time () - start_time)

Jon Harrop

unread,
Feb 15, 2009, 1:08:07 PM2/15/09
to
William James wrote:
> Jon Harrop wrote:
>> On the other hand, what you are really doing is complex-valued
>> arithmetic and, consequently, it would be interesting to compare the
>> performance of idiomatic code that uses the complex number
>> representations provided with OCaml and F#. You will find that F#
>> runs circles around OCaml there (e.g. FFT over complexes is 5.5x
>> faster in F# than OCaml) because OCaml cannot support structs
>> efficiently because it lacks JIT compilation (or whole program
>> optimization like MLton) so it is forced to box all complex numbers,
>> which is hugely inefficient in numerical routines.
>
> F# times on my 2 GHz laptop:
>
> 2.0629664 using ordinary floats
> 18.206179 using Math.Complex

I suspect you are using an old CLR. The latest one includes struct
optimizations that should halve the latter time.

> The F# code:
>
> open Math.Complex
>
> let runs = 100
> let max_iterations = 1000
>
> let iterate cmp =
> let rec loop (z:Math.complex) i =
> if i > max_iterations then
> 0
> else
> // Using magnitude is too slow.
> // if magnitude z >= 2.0 then
> if (z.r * z.r + z.i * z.i) >= 4.0 then
> i
> else
> loop (z * z + cmp) (i + 1)

Note that this uses general complex product rather than the specialized
complex square from the float-based alternative.

> in
> loop zero 1
>
> let mandelbrot n =
> for y = -39 to 38 do
> if 1 = n then printfn "";
> for x = -39 to 38 do
> let i = iterate
> (complex ((float y / 40.0) - 0.5) (float x / 40.0)) in
> if 1 = n then printf "%s" ( if 0 = i then "*" else " " )
> done
> done;;
>
> let start_time = Sys.time () in
> for i = 1 to runs do
> mandelbrot i
> done;
> printfn "\n%f" (Sys.time () - start_time)

Use System.Diagnostics.Stopwatch in F# and Unix.gettimeofday in OCaml to
measure real time.

I get:

2.1GHz Opteron 2352 in 32-bit Debian
OCaml float: 7.57s
OCaml complex: 7.63s
F# Mono 2.2 float: 4.49s
F# Mono 2.2 complex: 15.26s

2.1GHz Opteron 2352 in 64-bit Debian
OCaml float: 3.39s
OCaml complex: 7.12s
F# Mono 2.2 float: 4.40s
F# Mono 2.2 complex: 5.06s

2.2GHz Athlon64 X2 in 32-bit Win XP
F# float: 1.81s
F# complex: 4.77s

The performance of the machines is similar so F# is significantly faster
than OCaml. If you use an algorithm that benefits from a data structure
with unboxed floats (like the FFT) then that speed gap widens as F# becomes
much faster still. OCaml does a lot better on x64 with floats (as usual)
but is still slower.

For some reason, Microsoft's CLR retains the memory layout of structs when
they are used as local variables and, consequently, structs are several
times slower than necessary. Interestingly, LLVM 2.4 and beyond implements
first-class structs as aggregate values that can have complete freedom in
storage format when they are local variables. For example, they may be
stored entirely in registers and not via the stack, and they can be passed
to other functions in registers whereas the CLR passes them by pointer to
the current stack frame (which breaks tail calls that pass structs on the
CLR).

William James

unread,
Feb 15, 2009, 3:04:25 PM2/15/09
to
Jon Harrop wrote:

>
> Use System.Diagnostics.Stopwatch in F# and Unix.gettimeofday in OCaml
> to measure real time.

Why isn't Sys.time accurate?


let timer = System.Diagnostics.Stopwatch.StartNew () in


let start_time = Sys.time () in
for i = 1 to runs do
mandelbrot i
done;

printfn "\n%f" (Sys.time () - start_time);
printfn "%O" timer.Elapsed

--- output ---

// floats
2.01289440000002
00:00:02.1956882

// complex
18.196165
00:00:18.8280599

Jon Harrop

unread,
Feb 16, 2009, 12:13:45 PM2/16/09
to
William James wrote:
> Jon Harrop wrote:
>> Use System.Diagnostics.Stopwatch in F# and Unix.gettimeofday in OCaml
>> to measure real time.
>
> Why isn't Sys.time accurate?

It measures elapsed CPU time and, consequently, can fail to convey speedups
due to parallelism between the mutator and GC when they run concurrently.

> let timer = System.Diagnostics.Stopwatch.StartNew () in
> let start_time = Sys.time () in
> for i = 1 to runs do
> mandelbrot i
> done;
> printfn "\n%f" (Sys.time () - start_time);
> printfn "%O" timer.Elapsed
>
> --- output ---
>
> // floats
> 2.01289440000002
> 00:00:02.1956882
>
> // complex
> 18.196165
> 00:00:18.8280599

What version of the CLR are you running?

William James

unread,
Feb 16, 2009, 3:50:47 PM2/16/09
to
Jon Harrop wrote:

> >
> > // floats
> > 2.01289440000002
> > 00:00:02.1956882
> >
> > // complex
> > 18.196165
> > 00:00:18.8280599
>
> What version of the CLR are you running?

I don't know what CLR is.

MSR F# Interactive, (c) Microsoft Corporation, All Rights Reserved
F# Version 1.9.4.19, compiling for .NET Framework Version v2.0.50727

Jon Harrop

unread,
Feb 18, 2009, 11:39:23 AM2/18/09
to
William James wrote:
> MSR F# Interactive, (c) Microsoft Corporation, All Rights Reserved
> F# Version 1.9.4.19, compiling for .NET Framework Version v2.0.50727

Ok, that's the latest CLR but an old version of F#. I get 2x better
performance in the latter case using the latest F# (1.9.6.2).

George Neuner

unread,
Feb 19, 2009, 1:33:24 PM2/19/09
to
On 16 Feb 2009 20:50:47 GMT, "William James" <w_a_...@yahoo.com>
wrote:

"CLR" is Microsoft's Common Language Runtime. It's another name for
the .NET framework.

George

namekuseijin

unread,
Feb 19, 2009, 9:16:13 PM2/19/09
to
Amusing thread of two well-known comp.lang.lisp trolls. Nice to see
OCaml getting it this turn. :)

where's that much expected OCaml vs Ruby, William?

William James

unread,
Feb 20, 2009, 8:53:53 PM2/20/09
to
namekuseijin wrote:

> where's that much expected OCaml vs Ruby, William?

\|||/
(o o)
,----ooO--(_)-------.
| Please |
| don't feed the |
| TROLLs ! |
'--------------Ooo--'
|__|__|
|| ||
ooO Ooo


namekuseijin

unread,
Feb 26, 2009, 9:55:42 PM2/26/09
to

LOL, did Reiner Joswig lend you that? ;)

0 new messages