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

How­ever, 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 value types they are stack allo­cated which is much faster, and don’t need to be garbage collected.

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

As a sim­ple test, given 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 other 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!

Part­ing Thoughts…

Although this test shows that cre­at­ing large num­bers of records takes sig­nif­i­cantly longer than structs, in prac­tice how­ever, would you really care if gen­er­at­ing 10 mil­lions objects takes 3 sec­onds instead of 0.1? Is that likely 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 cases you won’t be gen­er­at­ing large num­ber of these objects fre­quently 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 (Value types are passed by value 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 application.

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 annotation
  • records can be used as part of stan­dard pat­tern match­ing, no need for when guards

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

Share
  • Carsten

    Hi,

    there are some minor issues here. First cre­at­ing the objects on heap is very cheap — it’s the GC step that’s caus­ing the pain and here I have to dis­agree with your con­clu­sion.
    3s vs. 0.1s is not a big deal? Really?
    IMHO this is a very big deal as 10mio. objects might be big if you think on big buisi­ness objects but from time to time you process *smaller* data like points or small val­ues. These are the places where F# really shine — pro­cess­ing large quan­ti­ties of data — and of course 3s vs. 0.1s. mat­ters a LOT in those cases.

  • Pingback: Contrasting F# and Elm’s record types | theburningmonk.com