Performance F#:从序列中删除重复项很慢
我正在尝试编写一个函数,从Performance F#:从序列中删除重复项很慢,performance,f#,Performance,F#,我正在尝试编写一个函数,从seqseq.isEmpty、seq.head和seq.tail中剔除由给定相等函数确定的连续重复项。seq.head和seq.tail速度较慢,因为它们都创建了一个新的枚举器实例,然后调用该实例。你最终得到了很多GC 一般来说,序列是只向前的,如果使用它们“就像列表的模式匹配”,性能就会变得非常低劣 看看你的代码|无->屈服!s创建一个新的枚举数,即使我们知道s为空。每个递归调用可能最终都会创建一个新的IEnumerable,然后直接从调用站点以yield!转换为枚举
seqseq.isEmpty、seq.head和seq.tail中剔除由给定相等函数确定的连续重复项。seq.head和seq.tail速度较慢,因为它们都创建了一个新的枚举器实例,然后调用该实例。你最终得到了很多GC
一般来说,序列是只向前的,如果使用它们“就像列表的模式匹配”,性能就会变得非常低劣
看看你的代码<代码>|无->屈服!s
创建一个新的枚举数,即使我们知道s为空。每个递归调用可能最终都会创建一个新的IEnumerable,然后直接从调用站点以yield!转换为枚举数 问题在于如何使用序列。所有这些结果,正面和反面都在旋转一个由迭代器分支而成的迭代器网络,当您最终在调用List.ofSeq
时将其具体化时,您在输入序列中的迭代次数超出了应有的量
每个Seq.heads
都不是简单地获取序列的第一个元素,而是获取序列尾部序列尾部的第一个元素,依此类推
检查此项-它将计算调用元素构造函数的次数:
let count = ref 0
Seq.init 1000 (fun i -> count := !count + 1; 1)
|> dedupeTakingLast (fun (x,y) -> x = y) None
|> List.ofSeq
顺便说一句,只需将所有的
seq
切换到列表
就可以立即运行。正如其他答案所说,seq
非常慢。然而,真正的问题是为什么要在这里使用seq
?特别是,您从一个列表开始,希望遍历整个列表,并希望在末尾创建一个新列表。似乎没有任何理由使用序列,除非您想使用特定于序列的功能。事实上,该国(强调我国):
序列是一种类型的所有元素的逻辑序列。当您有大量有序的数据收集,但不一定期望使用所有元素时,序列特别有用。单独的序列元素仅根据需要计算,因此在不使用所有元素的情况下,序列可以提供比列表更好的性能
性能问题来自对Seq.tail的嵌套调用。下面是要下载的源代码
这个答案的第一个版本是上述代码的递归版本,没有变异。这里有一个非常快速的方法,它使用库函数而不是Seq表达式 你的测试在我的电脑上运行只需0.007秒 它对第一个元素有一个相当恶劣的攻击,这个元素不能很好地工作,可以改进
let rec dedupe equalityfn prev (s:'a seq) : 'a seq =
if Seq.isEmpty s then
Seq.empty
else
let rest = Seq.skipWhile (equalityfn prev) s
let valid = Seq.takeWhile (equalityfn prev) s
let valid2 = if Seq.isEmpty valid then Seq.singleton prev else (Seq.last valid) |> Seq.singleton
let filtered = if Seq.isEmpty rest then Seq.empty else dedupe equalityfn (Seq.head rest) (rest)
Seq.append valid2 filtered
let t = [("a", 1); ("b", 2); ("b", 3); ("b", 4); ("c", 5)]
|> dedupe (fun (x1, y1) (x2, y2) -> x1=x2) ("asdfasdf",1)
|> List.ofSeq;;
#time
List.init 1000 (fun _ -> 1)
|> dedupe (fun x y -> x = y) (189234784)
|> List.ofSeq
#time;;
--> Timing now on
Real: 00:00:00.007, CPU: 00:00:00.006, GC gen0: 0, gen1: 0
val it : int list = [189234784; 1]
--> Timing now off
我也期待着一个非序号的答案。下面是另一个解决方案:
let t = [("a", 1); ("b", 2); ("b", 3); ("b", 4); ("c", 5)]
t |> Seq.groupBy fst |> Seq.map (snd >> Seq.last)
// dedupeTakingLast2 : ('a -> 'a -> bool) -> seq<'a> -> 'a list
let dedupeTakingLast2 equalityFn =
Seq.fold
<| fun deduped elem ->
match deduped with
| [] -> [ elem ]
| x :: xs -> if equalityFn x elem
then elem :: xs
else elem :: deduped
<| []
我在1M列表上测试:
Real: 00:00:00.000, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
val it : seq<int * int> = seq [(2, 2); (1, 1)]
Real:00:00.000,CPU:00:00.000,GC gen0:0,gen1:0,gen2:0
valit:seq=seq[(2,2);(1,1)]
为了有效利用输入类型Seq
,每个元素只需迭代一次,避免创建额外的序列
另一方面,为了有效地使用输出类型列表
,应该自由地使用cons
和tail
函数,这两个函数基本上都是免费的
结合这两个需求,我找到了这个解决方案:
let t = [("a", 1); ("b", 2); ("b", 3); ("b", 4); ("c", 5)]
t |> Seq.groupBy fst |> Seq.map (snd >> Seq.last)
// dedupeTakingLast2 : ('a -> 'a -> bool) -> seq<'a> -> 'a list
let dedupeTakingLast2 equalityFn =
Seq.fold
<| fun deduped elem ->
match deduped with
| [] -> [ elem ]
| x :: xs -> if equalityFn x elem
then elem :: xs
else elem :: deduped
<| []
这里是一个使用mapFold的实现,但需要传入一个不等于最后一个值的值。无需编写递归函数。应该运行得更快,但未经测试
let dedupe notLast equalityfn (s:'a seq) =
[notLast]
|> Seq.append s
|> Seq.mapFold
(fun prev item ->
if equalityfn prev item
then (None, item)
else (Some(prev), item))
(Seq.head s)
|> fst
|> Seq.choose id
let items = [("a", 1); ("b", 2); ("b", 3); ("b", 4); ("c", 5)]
let unique =
dedupe ("", 0) (fun (x1, x2) (y1, y2) -> x1 = y1) items
printfn "%A" unique
这是一个老问题,但我只是在寻找老例子来演示我一直在开发的新库。它是System.Linq.Enumerable的替代品,但它还有一个包装器来替代F#的Seq。它还没有完成,但它是polyfill,以匹配现有的API(即,不完整的材料只是转发到现有的功能) 可在nuget上的以下位置获取: 因此,我从您答案的底部提取了您修改过的Seq,并将其“转换”为Cistern.Linq.FSharp(这只是对“Linq.”的“Seq.”的搜索和替换),然后将其运行时与您原来的运行时进行了比较。水箱版本在50%的时间内运行良好(我得到约41%)
开放系统
开式水箱
开放系统诊断
让重复数据消除LastCystern均衡fn s=
s
|>林克,给我一些地图
|>fun x->Linq.append x[无]
|>林克成对
|>林克地图(乐趣(x,y)->
将(x,y)与
|(一些a,一些b)->(如果(等式fn a b)那么没有其他的一些a)
|(无)->x
|(无)
|>Linq.choose id
让DeduplicateTakingLastSeq等式fn s=
s
|>下面是一些地图
|>乐趣x->Seq.append x[无]
|>顺序:成对
|>序列图(乐趣(x,y)->
将(x,y)与
|(一些a,一些b)->(如果(等式fn a b)那么没有其他的一些a)
|(无)->x
|(无)
|>Seq.chooseid
让测试数据=
让迭代次数=1000次
设sw=Stopwatch.StartNew()
对于i=1的迭代
数据
|>f(乐趣x y->x=y)
|>表1.1
|>忽略
打印fn“%s%d”,其中sw.elapsedmillies
[]
让主argv=
让data=List.init 10000(乐趣->1)
对于i=1到5 do
测试数据“Seq”重复数据检测LastSeq
测试数据“存储池”重复数据消除最后存储池
0
List->Set normal way,如果您不关心订单的话。我想知道这是否对您有帮助:不幸的是,我从来没有时间测试它。我很惊讶这个新版本运行得这么快。每个Seq函数都在结果周围包装另一个枚举数,因此它们至少有5层。看到生成的程序集会很有趣。我的问题真的可能是:“我的自制Seq.*
有什么东西是我的自制Seq{…yield…}
没有的?”。对您的解决方案进行一个小小的优化
let t = [("a", 1); ("b", 2); ("b", 3); ("b", 4); ("c", 5)]
t |> Seq.groupBy fst |> Seq.map (snd >> Seq.last)
Real: 00:00:00.000, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
val it : seq<int * int> = seq [(2, 2); (1, 1)]
// dedupeTakingLast2 : ('a -> 'a -> bool) -> seq<'a> -> 'a list
let dedupeTakingLast2 equalityFn =
Seq.fold
<| fun deduped elem ->
match deduped with
| [] -> [ elem ]
| x :: xs -> if equalityFn x elem
then elem :: xs
else elem :: deduped
<| []
List.init 1000 (id)
|> dedupeTakingLast2 (fun x y -> x - (x % 10) = y - (y % 10))
|> List.iter (printfn "%i ")
// 999 989 979 969 etc...
let dedupe notLast equalityfn (s:'a seq) =
[notLast]
|> Seq.append s
|> Seq.mapFold
(fun prev item ->
if equalityfn prev item
then (None, item)
else (Some(prev), item))
(Seq.head s)
|> fst
|> Seq.choose id
let items = [("a", 1); ("b", 2); ("b", 3); ("b", 4); ("c", 5)]
let unique =
dedupe ("", 0) (fun (x1, x2) (y1, y2) -> x1 = y1) items
printfn "%A" unique
open System
open Cistern.Linq.FSharp
open System.Diagnostics
let dedupeTakingLastCistern equalityFn s =
s
|> Linq.map Some
|> fun x -> Linq.append x [None]
|> Linq.pairwise
|> Linq.map (fun (x,y) ->
match (x,y) with
| (Some a, Some b) -> (if (equalityFn a b) then None else Some a)
| (_,None) -> x
| _ -> None )
|> Linq.choose id
let dedupeTakingLastSeq equalityFn s =
s
|> Seq.map Some
|> fun x -> Seq.append x [None]
|> Seq.pairwise
|> Seq.map (fun (x,y) ->
match (x,y) with
| (Some a, Some b) -> (if (equalityFn a b) then None else Some a)
| (_,None) -> x
| _ -> None )
|> Seq.choose id
let test data which f =
let iterations = 1000
let sw = Stopwatch.StartNew ()
for i = 1 to iterations do
data
|> f (fun x y -> x = y)
|> List.ofSeq
|> ignore
printfn "%s %d" which sw.ElapsedMilliseconds
[<EntryPoint>]
let main argv =
let data = List.init 10000 (fun _ -> 1)
for i = 1 to 5 do
test data "Seq" dedupeTakingLastSeq
test data "Cistern" dedupeTakingLastCistern
0