这是我之前question关于Seq
模块的iter
和map
函数与Array
和{{{{{}}相比慢得多的后续行动1}}模块等价物。
查看源代码,我可以看到List
和isEmpty
等函数执行非常简单的类型检查,以便在使用length
之前优化数组和列表。< / p>
IEnumerator
在[<CompiledName("IsEmpty")>]
let isEmpty (source : seq<'T>) =
checkNonNull "source" source
match source with
| :? ('T[]) as a -> a.Length = 0
| :? list<'T> as a -> a.IsEmpty
| :? ICollection<'T> as a -> a.Count = 0
| _ ->
use ie = source.GetEnumerator()
not (ie.MoveNext())
[<CompiledName("Length")>]
let length (source : seq<'T>) =
checkNonNull "source" source
match source with
| :? ('T[]) as a -> a.Length
| :? ('T list) as a -> a.Length
| :? ICollection<'T> as a -> a.Count
| _ ->
use e = source.GetEnumerator()
let mutable state = 0
while e.MoveNext() do
state <- state + 1;
state
的情况下,可以采用相同的方法来大大提高其性能,当我shadowed iter
函数时,它比内置版本显着提升:
iter
我的问题是,鉴于[<CompiledName("Iterate")>]
let iter f (source : seq<'T>) =
checkNonNull "source" source
use e = source.GetEnumerator()
while e.MoveNext() do
f e.Current;
模块中的某些函数已针对特定集合类型(数组,列表&lt; T&gt;等)进行了优化,如何使用其他函数,例如{{1} }和Seq
没有以类似的方式进行优化?
此外,在iter
函数的情况下,正如@mausch指出的那样,是否不可能对nth
使用类似的方法(见下文)并为不同的集合类型构建专门的迭代器?
map
非常感谢提前。
答案 0 :(得分:4)
在iter的情况下,可以采用相同的方法来大大提高其性能
我认为这是你问题的答案所在。你的测试是人为的,并没有真正测试这些方法的任何现实世界的例子。您测试了10,000,000次迭代这些方法,以便在ms
中获得时间差异。
转换回每件商品的费用,这里是:
Array List
Seq.iter 4 ns 7 ns
Seq.map 20 ns 91 ns
这些方法通常每个集合使用一次,这意味着此成本是算法性能的另一个线性因素。在最糟糕的情况下,您在列表中的每个项目丢失的次数少于100 ns
(如果您非常关心性能,则不应使用该项目。)
将此与length
的情况进行对比,isEmpty
在一般情况下始终是线性的。通过添加此优化,您可以为忘记手动缓存长度的人提供巨大的好处,但幸运的是总是给出一个列表。
同样,您可以多次调用type Custom() =
interface IEnumerable with
member x.GetEnumerator() =
return seq {
yield 1
yield 2
}
interface IList with
member x.Item with
get(index) = index
member x.Count = 12
let a = Custom()
a |> Seq.iter (v -> printfn (v.ToString()))
,如果您可以直接询问,添加另一个对象创建是愚蠢的。 (这不是一个强有力的争论)
要记住的另一件事是,这些方法都不会实际查看输出的多个元素。您希望以下代码做什么(排除语法错误或缺少方法)
{{1}}
答案 1 :(得分:1)
从表面上看,Seq.length
/ isEmpty
中的类型检查似乎是错误的。我假设大多数Seq
函数不对正交性执行此类检查:List
/ Array
模块中已存在特定于类型的版本。为什么要复制它们?
这些检查在LINQ中更有意义,因为它只直接使用IEnumerable
。