Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Performance for composition in F#
Jakob Christensen
Jakob Christensen

Posted on • Edited on

     

Performance for composition in F#

In aprevious post 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 theResult type.

In other words, we will compare the following operators:

// Monadic bindlet(>>=)mf=Result.bindfm// Monadic bind inlineletinline(>>==)mf=Result.bindfm// Kleisliletinline(>=>)abx=matchaxwith|Okv->bv|Errore->Errore
Enter fullscreen modeExit fullscreen mode

To test we compose five different functions. All the functions do is copy some data structure:

typeData={Property1:stringProperty2:intProperty3:DateTimeProperty4:floatProperty5:decimal}letprocessAdata={datawithProperty1="some new string"}letprocessBdata={datawithProperty2=100}letprocessCdata=...letprocessResultAdata=Ok{datawithProperty1="some new string"}letprocessResultBdata=Ok{datawithProperty2=100}letprocessResultCdata=...
Enter fullscreen modeExit fullscreen mode

We use functionprocessData for testing|> andprocessDataResult for testing the compositions that operate onResult.

Now we can create the functions we wish to time:

letwithPipingdata=data|>processA|>processB|>processC|>processD|>processEletwithBindingdata=data|>processResultA|>Result.bindprocessResultB|>Result.bindprocessResultC|>Result.bindprocessResultD|>Result.bindprocessResulteletwithOperatorWithoutInliningdata=data|>processResultA>>=processResultB>>=processResultC>>=processResultD>>=processResultEletwithOperatorWithInliningdata=data|>processResultA>>==processResultB>>==processResultC>>==processResultD>>==processResultEletwithKleislidata=data|>(processResultA>=>processResultB>=>processResultC>=>processResultD>=>processResultE)
Enter fullscreen modeExit fullscreen mode

Note thatwithBinding,withOperatorWithoutInlining andwithOperatorWithInlining 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 functionf:

lettimeFunctionfdata=letsw=Stopwatch()sw.Start()[1..10_000_000]|>List.iter(fun_->fdata|>ignore)sw.Stop()sw.Elapsed.TotalMilliseconds
Enter fullscreen modeExit fullscreen mode

That way we can time 10 million calls to a function like so:

data|>timeFunctionwithKleisli
Enter fullscreen modeExit fullscreen mode

To get a smaller variance on those 10 millions calls, let us define arun function that runstimeFunction a number of times and prints the average:

letrunffName=letdata={Property1="some string"Property2=42Property3=DateTime.TodayProperty4=23.2Property5=23m}[1..100]|>List.averageBy(funi->timeFunctionfdata)|>printfName
Enter fullscreen modeExit fullscreen mode

Now we can write the final setup:

runwithPiping(nameofwithPiping)runwithBinding(nameofwithBinding)runwithOperatorWithInlining(nameofwithOperatorWithInlining)runwithOperatorWithoutInlining(nameofwithOperatorWithoutInlining)runwithKleisli(nameofwithKleisli)
Enter fullscreen modeExit fullscreen mode

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 setwithPiping as index 100, so the graph shows how the other methods compare towithPiping.

Graph showing relative performance for the different methods

It shouldn't come as a surprise thatwithPiping is the fastest.

I am a bit surprised thatwithBinding is that much faster thanwithOperatorWithoutInlining, 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.

Top comments(2)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
scotthutchinson profile image
Scott Hutchinson
  • Joined

Have you considered usinggithub.com/dotnet/BenchmarkDotNet for this comparison? There's a bit of a learning curve to get started with it, and it can take a while to run, but the reward is more robust, statistically significant results.

CollapseExpand
 
t4rzsan profile image
Jakob Christensen
Actuary with love for programming. Besides coding, I love Icelandic horses, math, and photography. All featured photos are my own.
  • Location
    Denmark
  • Education
    Master of Actuarial Science
  • Pronouns
    he/him/his
  • Work
    Actuary at AP Pension
  • Joined

Nice, thank you. I did not know about that one.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Actuary with love for programming. Besides coding, I love Icelandic horses, math, and photography. All featured photos are my own.
  • Location
    Denmark
  • Education
    Master of Actuarial Science
  • Pronouns
    he/him/his
  • Work
    Actuary at AP Pension
  • Joined

More fromJakob Christensen

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp