# Ocaml vs .NET Performance

**URL:** <https://forums.fsharp.org/t/ocaml-vs-net-performance/5183>\
**Category:** General\
**Tags:** beginners\
**Created:** [August 22, 2026, 2:50am UTC](https://forums.fsharp.org/t/ocaml-vs-net-performance/5183 "2026-08-22T02:50:22Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![altbodhi](https://yyz2.discourse-cdn.com/flex030/user_avatar/forums.fsharp.org/altbodhi/32/1253_2.png) [@altbodhi](https://forums.fsharp.org/u/altbodhi)\
**Post date:** [August 22, 2026, 2:50am UTC](https://forums.fsharp.org/t/ocaml-vs-net-performance/5183/1 "2026-08-22T02:50:22Z")

</div>

I became interested functor module of ocaml and wrote small program within:

```auto
module type ORDERED = sig
  type t
  val compare : t -> t -> int
end

module MakeSet (Elt : ORDERED) = struct
  type elt = Elt.t
  type t = elt list (* упорядоченный список без дублей *)

  let empty = []

  let rec add x = function
    | [] -> [x]
    | y :: rest as s ->
      let c = Elt.compare x y in
      if c = 0 then s (* уже есть *)
      else if c < 0 then x :: s (* вставка перед y *)
      else y :: add x rest

  let mem x s = List.exists (fun y -> Elt.compare x y = 0) s
end

module IntOrd = struct
  type t = int
  let compare = compare (* стандартное сравнение *)
end

module IntSet = MakeSet (IntOrd)

let rec loop n xs =
  if n < 1 then xs else loop (n - 1) (IntSet.add n xs)

let n = 10_000_000
let x = loop n IntSet.empty
let b = IntSet.mem n x
let () = Printf.printf "%b\ndone\n" b

```

```auto
let empty = []

let inline compare<'t when 't :> System.IComparable> (a: 't, b: 't) = a.CompareTo(b)

let rec add x =
    function
    | [] -> [x]
    | y :: rest as s ->
        let c = compare (x, y) in

        if c = 0 then
            s (* уже есть *)
        else if c < 0 then
            x :: s (* вставка перед y *)
        else
            y :: add x rest

let mem x s =
    List.exists (fun y -> compare (x, y) = 0) s

let rec loop n xs =
    if n < 1 then
        xs
    else
        loop (n - 1) (add n xs)

let n = 10_000_000

let x = loop n empty
let b = mem n x
do printf "%b\ndone\n" b

```

Measure-Command { & .\Test.exe } - 4-6 sec (target mscorlib)

Measure-Command { & .\camlprog.exe } - 2 sec

Why? Can I something change to reach ocaml performance?

---

<div class="post-metadata">

**Author:** ![killface](https://yyz2.discourse-cdn.com/flex030/user_avatar/forums.fsharp.org/killface/32/1444_2.png) [@killface](https://forums.fsharp.org/u/killface)\
**Post date:** [August 22, 2026, 4:02pm UTC](https://forums.fsharp.org/t/ocaml-vs-net-performance/5183/2 "2026-08-22T16:02:06Z")

</div>

I’m guessing the main contributor to the performance difference is the F# version currently boxing arguments passed to `CompareTo()`:

```fsharp
let inline compare<'t when 't :> System.IComparable> (a: 't, b: 't) = a.CompareTo(b)

```

`IComparable.CompareTo()` takes an object. The argument passed (in this case an int) needs to be boxed before the comparison is done.

Looking at the IL confirms this:

```auto
ldarg.1
box !!T
constrained. !!T
callvirt instance int32 System.IComparable::CompareTo(object)

```

If you change the compare line to…

```fsharp
let inline compare<'t when 't :> IComparable<'t>> (a: 't, b: 't) = a.CompareTo(b)

```

… then you can avoid the boxing penalty in this case.

IL confirmation (note the absent `box !!T`):

```auto
ldarga.s a
ldarg.1
constrained. !!T
callvirt instance int32 System.IComparable`1<!!T>::CompareTo(!0)

```

* * *

I ran a quick benchmark using .NET 10, and here’s what I got comparing the boxed vs non-boxed versions:

| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Original | 2,057.2 ms | 62.33 ms | 32.60 ms | 1.00 | 0.02 | 150000.0000 | 49000.0000 | 5000.0000 | 762.9 MB | 1.00 |
| GenericComparable | 757.2 ms | 15.63 ms | 8.18 ms | 0.37 | 0.01 | 54000.0000 | 28000.0000 | 3000.0000 | 305.18 MB | 0.40 |

> **Full benchmarking code**
>
> ```fsharp
> open System
> open BenchmarkDotNet.Attributes
> open BenchmarkDotNet.Running
> 
> module Original =
> 
> let empty: int list = []
> 
> let inline compare<'T when 'T :> IComparable> (a: 'T, b: 'T) = a.CompareTo(b)
> 
> let rec add x =
> function
> | [] -> [x]
> | y :: rest as s ->
> let c = compare (x, y)
> 
> if c = 0 then s
> elif c < 0 then x :: s
> else y :: add x rest
> 
> let mem x s =
> List.exists (fun y -> compare (x, y) = 0) s
> 
> let rec loop n xs =
> if n < 1 then xs else loop (n - 1) (add n xs)
> 
> let run n =
> let x = loop n empty
> mem n x
> 
> module GenericComparable =
> 
> let empty: int list = []
> 
> // Generic IComparable<T>; CompareTo takes T rather than obj.
> let inline compare<'T when 'T :> IComparable<'T>> (a: 'T, b: 'T) = a.CompareTo(b)
> 
> let rec add x =
> function
> | [] -> [x]
> | y :: rest as s ->
> let c = compare (x, y)
> 
> if c = 0 then s
> elif c < 0 then x :: s
> else y :: add x rest
> 
> let mem x s =
> List.exists (fun y -> compare (x, y) = 0) s
> 
> let rec loop n xs =
> if n < 1 then xs else loop (n - 1) (add n xs)
> 
> let run n =
> let x = loop n empty
> mem n x
> 
> [<MemoryDiagnoser>]
> [<SimpleJob (launchCount = 1, warmupCount = 3, iterationCount = 10, invocationCount = 1)>]
> type CompareBenchmarks () =
> 
> let n = 10_000_000
> 
> [<Benchmark(Baseline = true)>]
> member _.Original() = Original.run n
> 
> [<Benchmark>]
> member _.GenericComparable() = GenericComparable.run n
> 
> [<EntryPoint>]
> let main _ =
> BenchmarkRunner.Run<CompareBenchmarks>() |> ignore
> 
> 0
> 
> ```

---

<div class="post-metadata">

**Author:** ![altbodhi](https://yyz2.discourse-cdn.com/flex030/user_avatar/forums.fsharp.org/altbodhi/32/1253_2.png) [@altbodhi](https://forums.fsharp.org/u/altbodhi)\
**Post date:** [August 22, 2026, 9:59pm UTC](https://forums.fsharp.org/t/ocaml-vs-net-performance/5183/3 "2026-08-22T21:59:34Z")

</div>

OMG! Thank you very much. Very small detail but very important!
