Soldalma
Soldalma

Reputation: 4758

Passing mutable variables as arguments wastes memory?

I wonder if using mutable variables can lead to memory being wasted.

Consider the following two examples, whose output (the values a, b, and c) should be identical:

// Example 1

let mutable mut = Map.empty

mut <- mut |> Map.add "A" 0
let fA (m: Map<string,int>) x = m.["A"] + x
let a = fA mut 0 // 0

mut <- mut |> Map.add "B" 1
let fB (m: Map<string,int>) x = m.["A"] + m.["B"] + x
let b = fB mut 0 // 1

mut <- mut |> Map.add "C" 2
let fC (m: Map<string,int>) x = m.["A"] + m.["B"] + m.["C"] + x
let c = fC mut 0 // 3

In Example 1 each function takes a mutable argument and must (I presume) make a copy of that argument. in total three copies are made.

// Example 2
let mutable mut = Map.empty

mut <- mut |> Map.add "A" 0
mut <- mut |> Map.add "B" 1
mut <- mut |> Map.add "C" 2

let fA (m: Map<string,int>) x = m.["A"] + x
let fB (m: Map<string,int>) x = m.["A"] + m.["B"] + x
let fC (m: Map<string,int>) x = m.["A"] + m.["B"] + m.["C"] + x

let immut = mut

let a = fA mut 0 // 0
let b = fB mut 0 // 1
let c = fC mut 0 // 3

In Example 2 each function makes a copy of the same immutable argument. Presumably the compiler is smart enough not to use any additional memory when making these copies. For each copy could be just a pointer to the original object.

So even though on average the objects copied in Example 1 are smaller than the immutable object in Example 2, less memory will be used in Example 2.

Is this reasoning correct?

Upvotes: 1

Views: 89

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243051

In your example, the Map<'K, 'V> value is immutable and the only mutable thing is the reference mut that you are using to keep a reference to the current instance of the Map<'K, 'V> value. The compiler does not need to make a copy of the value (I don't think there is a case where the F# compiler would make a copy behind your back - aside from value types).

This means that your two examples are pretty much the same - the only real difference is that in Example 2, you are passing a map containing more values to the three functions, so the lookup may be slightly longer (but that's a theoretical issue at best).

The issue you are hinting at could happen if you implemented the code as follows using a mutable data structure (resize array) and explicit copying:

let data = ResizeArray<string * int>()

data.Add("A", 0)
let f1 = 
  let lookup = dict data 
  fun x -> lookup.[x]

data.Add("B", 1)
let f2 = 
  let lookup = dict data 
  fun x -> lookup.[x]

f1 "A" // = 0
f1 "B" // error
f2 "A" // = 0
f2 "B" // = 1

Upvotes: 3

Related Questions