将两个 lists/arrays 映射到单个 list/array 的不同实现的性能

Performance of different implementations of mapping two lists/arrays to a single list/array

以下三个实现(abc)给出相同的结果

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

我正在尝试确定哪种实现在速度方面是最好的。

这三个实现真的一样吗?

映射实际上是否产生了 l1l2 的元素的元组,或者它是对 l1l2 的引用被送入映射器?

如果将 List 更改为 Array,结果是否会发生变化?

bc 的计算完全相同。来自 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 相似。请注意,这不是一个非常精确的测量,在随后的运行中,我得到了 bc.

大约 0.080

换句话说,b 数组:

Real: 00:00:00.009, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0

由于列表中每个项目的对象开销,数组以很大的优势获胜。因此,这种迭代的一次性缓冲区强烈倾向于使用数组来提高性能。不过,数组需要大对象分配,并最终取消分配,因此在某些情况下,重新使用现有数组可以带来另一个加速。

但是使用数组,尤其是改变它们,会使程序的功能变差。即使在这种情况下,数组的速度要快十几倍,这也可能是为了帮助编译器优化而付出的高昂代价。 永远记住 Knuth 关于过早优化的观点。在极少数情况下担心这些事情比简洁性、易读性、稳健性等更重要