Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/fsharp/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Performance F#:从序列中删除重复项很慢_Performance_F# - Fatal编程技术网

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