Thomas
Thomas

Reputation: 12107

making a mutable struct, in F#, vs. a class

Let's imagine this recursive function:

let rec doSomething dataList b c d e f g h i =
    // go through dataList and recurse with updated parameters

So we can make it cleaner:

type Context = { b:..; c:..; ... }
let rec doSomething dataList context =
    // recurse with an updated context object

In practice this is slower since the context object has to be re-created every time and with very tight loops with simple calculations the overhead is visible.

I have several math loops done like this but now I have a new scenario coming up:

I need to use them on data that gets streamed in by using the same logic, but step by step as the data is coming.

So my function would be something like:

let doOneStepOfSomething oneData context : result * Context =
    process one piece of data and returns the context object for the next iteration

But then I have also the object re-creation issue and I need to keep different contexts.

Is that a valid use case for a class where each instance could have its context variables as mutable? or would it make sense to make a mutable type with mutable fields instead for the context?

Upvotes: 1

Views: 97

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243051

For any performance related question, the only answer is that you have to test it to see. I tested four different approaches using a simple function and the #time feature.

Immutable functional record

type C = { a:int; b:int; c:int; d:int }

let rec foo c = 
  if c.a > 100000000 then c
  else foo { a = c.a + 1; b = c.a; c = c.a; d = c.a }

foo { a=0; b=0; c=0; d=0 } // ~1.1 sec

Function with inline parameters

let rec bar a b c d = 
  if a > 100000000 then (a,b,c,d)
  else bar (a+1) a a a 

bar 0 0 0 0 // ~200ms

Using an immutable struct to replace record

[<Struct>]
type S(a:int,b:int,c:int,d:int) = 
  member x.A = a
  member x.B = b
  member x.C = c
  member x.D = d

let rec goo (c:S) = 
  if c.A > 100000000 then c
  else goo (S(c.A+1, c.A, c.A, c.A))

goo (S(0,0,0,0)) // ~1.6sec

Using a mutable record

type M = { mutable a:int; mutable b:int; mutable c:int; mutable d:int }

let rec zoo c = 
  if c.a > 100000000 then c
  else 
  c.a <- c.a + 1
  c.b <- c.a
  c.c <- c.a
  c.d <- c.a
  zoo c

zoo { a=0; b=0; c=0; d=0 } // ~1 sec

From these four tests, it seems that inline parameters are more efficient than anything else (which is pretty much all the same). So I would just use an immutable record, which makes the code clear and optimize this only if it proved to be an issue more generally in your system.

Upvotes: 3

Related Questions