将两个列表/数组映射到单个列表/数组的不同实现的性能

时间:2015-01-19 13:26:04

标签: f#

以下三种实施方式(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

,结果会发生变化吗?

1 个答案:

答案 0 :(得分:1)

bc的计算完全相同。来自F#源代码:

let inline (||>) (x1,x2) f = f x1 x2

如名称所暗示的那样,内联函数内联调用,在进一步编译之前将c的表达式转换为b的表达式。

a在结果方面是等效的,但是当与"实际",更大的数据一起使用时,我预计它会更慢。我不希望编译器足够聪明,可以将压缩和投影合并到一个函数中,因此会有两次迭代,可能会分配一个完整的中间列表。

对于只是迭代批量数据,数组通常比列表更快。但是对于数组,你必须更加谨慎地将数据传递给谁,因为它们是可变的,与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相似。请注意,这不是一个非常精确的衡量标准,在0.080b的后续运行中c秒。

将它放在透视图中,b与数组:

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

由于列表中每个项目的对象开销,该数组大幅增加。因此,这种类型的迭代抛弃缓冲区强烈希望数组具有性能。尽管如此,数组需要大对象分配,并最终需要解除分配,因此在某些情况下,重新使用现有数组可以带来另一个加速。

但是使用数组并特别改变它们会使程序失去功能。即使在像这样的情况下,阵列的速度要快十几倍,但为了帮助编译器优化,这可能是一个很高的代价。 永远记住Knuth过早优化。在极少数情况下担心这些事情,它们比简洁,易读,稳健等更重要。