在函数之间传递大型数据结构的 F# 效率影响
F# efficiency implications of passing large data structures between functions
F# 如何将数据从调用函数传递到被调用函数?它是在移交数据之前复制数据还是只是传递一个指针?我会认为后者,但想确定一下。
在相关说明中,以下 2 种 F# 代码样式是否对性能有任何影响。
let someFunction e =
1//pretend this is a complicated function
let someOtherFunction e =
2//pretend this is a complicated function
let foo f largeList=
List.map (fun elem -> f elem)
let bar largeList =
largeList
|> foo someFunction
|> foo someOtherFunction
let bar2 largeList =
let foo2 f f2 =
largeList
|> List.map (fun elem -> f elem)
|> List.map (fun elem -> f2 elem)
foo2 someFunction someOtherFunction
您希望 bar 的性能与 bar2 不同吗?如果没有,我应该注意哪些情况会有所不同?
简答:
没有。不会复制整个列表,只是对它的引用。
长答案:
在 F# 中(就像在 C# 中一样)值和引用类型都可以通过值或引用传递。
默认情况下,值类型和引用类型都是按值传递的。
对于值类型(结构),这意味着您将
传递整个数据结构的副本。
在引用类型(类、可区分联合、记录等)的情况下,这意味着引用是按值传递的.这并不意味着复制了整个数据结构,它只是意味着复制了引用数据结构的int
/int64
。
如果您正在使用可变数据结构,例如ResizeArray<'T>
(.NET List<'T>
) 类,按值传递引用可能会产生影响。例如,也许您传递给它的函数将元素添加到列表中?这样的更新将应用于从两个位置引用的数据结构。由于您的问题使用了不可变的 F# List,因此您不必担心这个!
您还可以通过引用传递 value/reference 类型,有关详细信息,请参阅:https://msdn.microsoft.com/en-us/library/dd233213.aspx#Anchor_4
F# list 是作为单链表实现的,这意味着访问头部和前置操作是 O(1)。这些数据结构的内存效率也很高,因为当您将一个元素添加到列表中时,您只需要存储新值和对列表其余部分的引用。
所以你可以看到它是如何工作的,这样的数据结构可以这样实现:
type ExampleList<'T> =
|Empty
|Cons of 'T * List<'T>
附加信息:
List.map
被急切求值,这意味着每次调用它时,都会创建一个新列表。如果您使用 Seq.map
(F# List 实现了 IEnumerable<'T>
接口),它是延迟计算的,您可以仅在列表的枚举中计算这两个映射操作。
largeList
|> Seq.map (fun elem -> f elem)
|> Seq.map (fun elem -> f2 elem)
|> List.ofSeq
这对于大型列表来说可能更有效,因为它只涉及分配一个新的结果列表,而不是两个。
F# 如何将数据从调用函数传递到被调用函数?它是在移交数据之前复制数据还是只是传递一个指针?我会认为后者,但想确定一下。 在相关说明中,以下 2 种 F# 代码样式是否对性能有任何影响。
let someFunction e =
1//pretend this is a complicated function
let someOtherFunction e =
2//pretend this is a complicated function
let foo f largeList=
List.map (fun elem -> f elem)
let bar largeList =
largeList
|> foo someFunction
|> foo someOtherFunction
let bar2 largeList =
let foo2 f f2 =
largeList
|> List.map (fun elem -> f elem)
|> List.map (fun elem -> f2 elem)
foo2 someFunction someOtherFunction
您希望 bar 的性能与 bar2 不同吗?如果没有,我应该注意哪些情况会有所不同?
简答:
没有。不会复制整个列表,只是对它的引用。
长答案:
在 F# 中(就像在 C# 中一样)值和引用类型都可以通过值或引用传递。
默认情况下,值类型和引用类型都是按值传递的。
对于值类型(结构),这意味着您将 传递整个数据结构的副本。
在引用类型(类、可区分联合、记录等)的情况下,这意味着引用是按值传递的.这并不意味着复制了整个数据结构,它只是意味着复制了引用数据结构的
int
/int64
。
如果您正在使用可变数据结构,例如ResizeArray<'T>
(.NET List<'T>
) 类,按值传递引用可能会产生影响。例如,也许您传递给它的函数将元素添加到列表中?这样的更新将应用于从两个位置引用的数据结构。由于您的问题使用了不可变的 F# List,因此您不必担心这个!
您还可以通过引用传递 value/reference 类型,有关详细信息,请参阅:https://msdn.microsoft.com/en-us/library/dd233213.aspx#Anchor_4
F# list 是作为单链表实现的,这意味着访问头部和前置操作是 O(1)。这些数据结构的内存效率也很高,因为当您将一个元素添加到列表中时,您只需要存储新值和对列表其余部分的引用。
所以你可以看到它是如何工作的,这样的数据结构可以这样实现:
type ExampleList<'T> =
|Empty
|Cons of 'T * List<'T>
附加信息:
List.map
被急切求值,这意味着每次调用它时,都会创建一个新列表。如果您使用 Seq.map
(F# List 实现了 IEnumerable<'T>
接口),它是延迟计算的,您可以仅在列表的枚举中计算这两个映射操作。
largeList
|> Seq.map (fun elem -> f elem)
|> Seq.map (fun elem -> f2 elem)
|> List.ofSeq
这对于大型列表来说可能更有效,因为它只涉及分配一个新的结果列表,而不是两个。