将两个 lists/arrays 映射到单个 list/array 的不同实现的性能
Performance of different implementations of mapping two lists/arrays to a single list/array
以下三个实现(a
、b
、c
)给出相同的结果
let l1 = [1..10]
let l2 = [11..20]
let avg1 = fun (x, y) -> (x+y)/2
let avg2 x y = (x+y)/2
let a = l1 |> List.zip l2 |> List.map avg1
let b = List.map2 avg2 l1 l2
let c = (l1, l2) ||> List.map2 avg2
我正在尝试确定哪种实现在速度方面是最好的。
这三个实现真的一样吗?
映射实际上是否产生了 l1
和 l2
的元素的元组,或者它是对 l1
和 l2
的引用被送入映射器?
如果将 List
更改为 Array
,结果是否会发生变化?
b
和 c
的计算完全相同。来自 F# 源代码:
let inline (||>) (x1,x2) f = f x1 x2
内联函数,顾名思义,在调用的地方内联,在进一步编译之前将 c
的表达式转换为 b
的表达式。
a
就结果而言是等效的,但是当与 "actual" 一起使用时,更大的数据,我预计它会更慢。我不希望编译器足够聪明,可以将压缩和投影合并到一个函数中,因此会有两次迭代,可能会分配整个中间列表。
对于仅迭代批量数据,数组通常比列表更快。但是对于数组,您必须更加小心将数据传递给谁,因为它们是可变的,这与 F# 列表不同。
可以从 F# interactive 中获得粗略的性能估计。启用 [1..1000000]
、[1000001..2000000]
和 #time
列表后,a
的成本显示为:
Real: 00:00:00.387, CPU: 00:00:00.436, GC gen0: 9, gen1: 4, gen2: 1
和b
确认加速:
Real: 00:00:00.149, CPU: 00:00:00.140, GC gen0: 3, gen1: 2, gen2: 0
c
与预期的 b
相似。请注意,这不是一个非常精确的测量,在随后的运行中,我得到了 b
或 c
.
大约 0.080
秒
换句话说,b
数组:
Real: 00:00:00.009, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0
由于列表中每个项目的对象开销,数组以很大的优势获胜。因此,这种迭代的一次性缓冲区强烈倾向于使用数组来提高性能。不过,数组需要大对象分配,并最终取消分配,因此在某些情况下,重新使用现有数组可以带来另一个加速。
但是使用数组,尤其是改变它们,会使程序的功能变差。即使在这种情况下,数组的速度要快十几倍,这也可能是为了帮助编译器优化而付出的高昂代价。 永远记住 Knuth 关于过早优化的观点。在极少数情况下担心这些事情比简洁性、易读性、稳健性等更重要
以下三个实现(a
、b
、c
)给出相同的结果
let l1 = [1..10]
let l2 = [11..20]
let avg1 = fun (x, y) -> (x+y)/2
let avg2 x y = (x+y)/2
let a = l1 |> List.zip l2 |> List.map avg1
let b = List.map2 avg2 l1 l2
let c = (l1, l2) ||> List.map2 avg2
我正在尝试确定哪种实现在速度方面是最好的。
这三个实现真的一样吗?
映射实际上是否产生了 l1
和 l2
的元素的元组,或者它是对 l1
和 l2
的引用被送入映射器?
如果将 List
更改为 Array
,结果是否会发生变化?
b
和 c
的计算完全相同。来自 F# 源代码:
let inline (||>) (x1,x2) f = f x1 x2
内联函数,顾名思义,在调用的地方内联,在进一步编译之前将 c
的表达式转换为 b
的表达式。
a
就结果而言是等效的,但是当与 "actual" 一起使用时,更大的数据,我预计它会更慢。我不希望编译器足够聪明,可以将压缩和投影合并到一个函数中,因此会有两次迭代,可能会分配整个中间列表。
对于仅迭代批量数据,数组通常比列表更快。但是对于数组,您必须更加小心将数据传递给谁,因为它们是可变的,这与 F# 列表不同。
可以从 F# interactive 中获得粗略的性能估计。启用 [1..1000000]
、[1000001..2000000]
和 #time
列表后,a
的成本显示为:
Real: 00:00:00.387, CPU: 00:00:00.436, GC gen0: 9, gen1: 4, gen2: 1
和b
确认加速:
Real: 00:00:00.149, CPU: 00:00:00.140, GC gen0: 3, gen1: 2, gen2: 0
c
与预期的 b
相似。请注意,这不是一个非常精确的测量,在随后的运行中,我得到了 b
或 c
.
0.080
秒
换句话说,b
数组:
Real: 00:00:00.009, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0
由于列表中每个项目的对象开销,数组以很大的优势获胜。因此,这种迭代的一次性缓冲区强烈倾向于使用数组来提高性能。不过,数组需要大对象分配,并最终取消分配,因此在某些情况下,重新使用现有数组可以带来另一个加速。
但是使用数组,尤其是改变它们,会使程序的功能变差。即使在这种情况下,数组的速度要快十几倍,这也可能是为了帮助编译器优化而付出的高昂代价。 永远记住 Knuth 关于过早优化的观点。在极少数情况下担心这些事情比简洁性、易读性、稳健性等更重要