以下三种实施方式(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
?
答案 0 :(得分:1)
b
和c
的计算完全相同。来自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.080
或b
的后续运行中c
秒。
将它放在透视图中,b
与数组:
Real: 00:00:00.009, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0
由于列表中每个项目的对象开销,该数组大幅增加。因此,这种类型的迭代抛弃缓冲区强烈希望数组具有性能。尽管如此,数组需要大对象分配,并最终需要解除分配,因此在某些情况下,重新使用现有数组可以带来另一个加速。
但是使用数组并特别改变它们会使程序失去功能。即使在像这样的情况下,阵列的速度要快十几倍,但为了帮助编译器优化,这可能是一个很高的代价。 永远记住Knuth过早优化。在极少数情况下担心这些事情,它们比简洁,易读,稳健等更重要。