将可变变量作为参数传递会浪费内存吗?
Passing mutable variables as arguments wastes memory?
我想知道使用可变变量是否会导致内存被浪费。
考虑以下两个示例,其输出(值 a
、b
和 c
)应该相同:
// 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
在 Example 1
中,每个函数都有一个可变参数并且必须(我假设)复制该参数。
一共做了三份
// 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
在 Example 2
中,每个函数都会复制相同的不可变参数。
据推测,编译器足够聪明,不会在制作这些副本时使用任何额外的内存。对于每个副本都可以只是指向原始对象的指针。
因此,即使 Example 1
中复制的对象平均小于 Example 2
中的不可变对象,Example 2
中使用的内存也较少。
这个推理正确吗?
在您的示例中,Map<'K, 'V>
值是不可变的,唯一可变的是您用来保持对 Map<'K, 'V>
当前实例的引用的引用 mut
价值。编译器不需要制作值的副本(我不认为 F# 编译器会在你背后制作副本 - 除了值类型)。
这意味着您的两个示例几乎相同 - 唯一真正的区别是在示例 2 中,您将包含更多值的映射传递给三个函数,因此查找可能会稍微长一些(但那是充其量只是一个理论问题)。
如果您使用可变数据结构(调整数组大小)和显式复制按如下方式实现代码,则可能会发生您暗示的问题:
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
我想知道使用可变变量是否会导致内存被浪费。
考虑以下两个示例,其输出(值 a
、b
和 c
)应该相同:
// 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
在 Example 1
中,每个函数都有一个可变参数并且必须(我假设)复制该参数。
一共做了三份
// 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
在 Example 2
中,每个函数都会复制相同的不可变参数。
据推测,编译器足够聪明,不会在制作这些副本时使用任何额外的内存。对于每个副本都可以只是指向原始对象的指针。
因此,即使 Example 1
中复制的对象平均小于 Example 2
中的不可变对象,Example 2
中使用的内存也较少。
这个推理正确吗?
在您的示例中,Map<'K, 'V>
值是不可变的,唯一可变的是您用来保持对 Map<'K, 'V>
当前实例的引用的引用 mut
价值。编译器不需要制作值的副本(我不认为 F# 编译器会在你背后制作副本 - 除了值类型)。
这意味着您的两个示例几乎相同 - 唯一真正的区别是在示例 2 中,您将包含更多值的映射传递给三个函数,因此查找可能会稍微长一些(但那是充其量只是一个理论问题)。
如果您使用可变数据结构(调整数组大小)和显式复制按如下方式实现代码,则可能会发生您暗示的问题:
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