Performance for composition in F#
Tags: fsharp, performance, dotnet
Published: 2021-11-07 22:51:43

In a [previous post](https://dev.to/t4rzsan/real-life-performance-optimizations-in-f-3nep) I talked about how we at work simplified composition in an F# project because we found that we took a performance hit when we used monadic binds in our code.

I was a bit surprised how expensive the construct was so I wanted to dig deeper into it with this article.

## The setup
Composition in F# can take a number of forms.  Below we will take a closer look at the simple straight forward pipe (`|>`) operator and compare it to the Kleisli composition and monadic bind composition operating on the [`Result`](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/results) type.

In other words, we will compare the following operators:
```fsharp
// Monadic bind
let (>>=) m f =
    Result.bind f m 

// Monadic bind inline
let inline (>>==) m f =
    Result.bind f m 

// Kleisli
let inline (>=>) a b x =
    match a x with
    | Ok v -> b v
    | Error e -> Error e
```
To test we compose five different functions.  All the functions do is copy some data structure:
```fsharp
type Data =
    { Property1: string
      Property2: int
      Property3: DateTime 
      Property4: float
      Property5: decimal }

let processA data = { data with Property1 = "some new string" }
let processB data = { data with Property2 = 100 }
let processC data = ...

let processResultA data = Ok { data with Property1 = "some new string" }
let processResultB data = Ok { data with Property2 = 100 }
let processResultC data = ...

```
We use function `processData` for testing `|>` and `processDataResult` for testing the compositions that operate on `Result`.

Now we can create the functions we wish to time:
```fsharp
let withPiping data =
    data
    |> processA
    |> processB
    |> processC
    |> processD
    |> processE

let withBinding data =
    data
    |> processResultA
    |> Result.bind processResultB
    |> Result.bind processResultC
    |> Result.bind processResultD
    |> Result.bind processResulte

let withOperatorWithoutInlining data =
    data
    |> processResultA
    >>= processResultB
    >>= processResultC
    >>= processResultD
    >>= processResultE

let withOperatorWithInlining data =
    data
    |> processResultA
    >>== processResultB
    >>== processResultC
    >>== processResultD
    >>== processResultE

let withKleisli data =
    data
    |> (processResultA
    >=> processResultB
    >=> processResultC
    >=> processResultD
    >=> processResultE)
```
Note that `withBinding`, `withOperatorWithoutInlining` and `withOperatorWithInlining` are essentially the same but as we will see later there is a difference in performance between them.

Let's also define a function for timing a number of calls to a function `f`:
```fsharp
let timeFunction f data =
    let sw = Stopwatch()
       
    sw.Start()
    
    [1 .. 10_000_000]
    |> List.iter (fun _ -> f data |> ignore)
    
    sw.Stop()
    sw.Elapsed.TotalMilliseconds
```
That way we can time 10 million calls to a function like so:
```fsharp
data |> timeFunction withKleisli
```
To get a smaller variance on those 10 millions calls, let us define a `run` function that runs `timeFunction` a number of times and prints the average:
```fsharp
let run f fName =
    let data =
        { Property1 = "some string"
          Property2 = 42
          Property3 = DateTime.Today
          Property4 = 23.2
          Property5 = 23m }

    [1 .. 100]
    |> List.averageBy (fun i -> timeFunction f data)
    |> print fName
```
Now we can write the final setup:
```fsharp
run withPiping (nameof withPiping)
run withBinding (nameof withBinding)
run withOperatorWithInlining (nameof withOperatorWithInlining)
run withOperatorWithoutInlining (nameof withOperatorWithoutInlining)
run withKleisli (nameof withKleisli)
```
Granted, the setup is a bit lame but it does give some indication of differences in performance between the different types of composition.

## The results are in
They say that an image is worth a thousand words, so here is a graph for you.  I set `withPiping` as index 100, so the graph shows how the other methods compare to `withPiping`.

![Graph showing relative performance for the different methods](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4htp8pfr36q403nmssll.jpg)

It shouldn't come as a surprise that `withPiping` is the fastest.  

I am a bit surprised that `withBinding` is that much faster than `withOperatorWithoutInlining`, i.e. the `>>=` operator.  

Kleisli composition is surprisingly slow, but monadic bind is more useful in F# anyways so you would probably not use Kleisli that often.

## Code
I put the code on Github if you want to have a look. I created it on .NET 6 RC using the preview of Visual Studio 2022.
{% github t4rzsan/fsharp-performance-testing no-readme %}