F# Performance Test — Structs vs Records

In F#, you have the choice of using a struct or a record as a light­weight con­tain­er for data. The sim­i­lar­i­ties between the two are strik­ing – both are immutable by default, nei­ther can be inher­it­ed, and they both offer struc­tur­al equal­i­ty seman­tics by default too!

How­ev­er, there’s a key dif­fer­ence between them, their per­for­mance char­ac­ter­is­tics.

When you’re deal­ing with tons of small objects, structs offer sig­nif­i­cant per­for­mance ben­e­fits because as val­ue types they are stack allo­cat­ed which is much faster, and don’t need to be garbage col­lect­ed.

Records on the oth­er hand, are ref­er­ence types, and are there­fore heap allo­cat­ed (plus point­ers on the stack), which is slow­er, and require the extra step of garbage col­lec­tion when they’re no longer ref­er­enced.

As a sim­ple test, giv­en these two iden­ti­cal types, one as a struct and one as a record:

image

The snip­pet below con­structs two arrays each with 10 mil­lion items, one with struct instances and the oth­er with record instances:

image

Over three runs, the structs array took an aver­age of 0.146 sec­onds to con­struct whilst the records array took an aver­age of 2.919 sec­onds!

Parting Thoughts…

Although this test shows that cre­at­ing large num­bers of records takes sig­nif­i­cant­ly longer than structs, in prac­tice how­ev­er, would you real­ly care if gen­er­at­ing 10 mil­lions objects takes 3 sec­onds instead of 0.1? Is that like­ly to be the source of your per­for­mance issues?

All and all, the per­for­mance gains you get by using structs over records is neg­li­gi­ble, in most cas­es you won’t be gen­er­at­ing large num­ber of these objects fre­quent­ly enough for you to notice the dif­fer­ence. Also, we haven’t even cov­ered the cost of copy­ing them when pass­ing them as para­me­ters (Val­ue types are passed by val­ue as opposed to ref­er­ence), which if you’re not care­ful can have a detri­men­tal effect on the over­all per­for­mance of your appli­ca­tion.

Also, records have two very use­ful fea­tures for work­ing with F# which structs don’t:

  • type infer­ence can infer a record’s type, no need for type anno­ta­tion
  • records can be used as part of stan­dard pat­tern match­ing, no need for when guards

both are big plus­es in my book and worth con­sid­er­ing when you’re choos­ing between records and structs.