【问题标题】:Need help optimizing my code f# (creating a array of tuples)需要帮助优化我的代码 f#(创建元组数组)
【发布时间】:2014-08-26 21:35:23
【问题描述】:

我需要帮助优化此代码,这需要 12 秒,我需要大约 4 秒。

let publication idx (lst: string [] list) = // returns a specific value of the string [] list
    lst
    |>Array.map (fun arr -> arr.[idx])


let rec Tuple (x:int) (ID:string list) (Information:string [] list) =
 if x < ID.Length then  
      let muro =  [|(ID.[x], Information|> List.filter (fun elem -> elem.[1] = humanID.[x] )|> publication 0   |> List.toArray )|]
      let rest = Tuple (x+1) ID Information
      Array.append muro rest     
 else  [||] 
let FinalTuple= Tuple 0 ID Information

finalTuple 是一个:(string*string []) []

递归需要很长时间才能完成,我似乎无法让它更快(ID.Length 为 1600)

感谢您的帮助

【问题讨论】:

  • 为什么要将列表转换为数组并返回?这是在浪费大量时间。
  • 解决了这个问题,没有什么影响
  • 你还在publication做这件事
  • 再次修复了这个问题,我这样做是因为几天前有人告诉我,在数组上映射比在列表上映射更快(仍然没有区别)
  • 当然,但它更快地转换为数组、映射然后再转换回列表。

标签: list optimization recursion f# tuples


【解决方案1】:

我同意 @mydogisbox 的一般观点 - 从数组到列表来回切换不会对性能有太大帮助。
话虽这么说,我遇到的第一个问题是,我不得不挖掘一下以弄清楚代码在做什么 - 所以我冒昧地进行了第一次重写,只是为了看看我是否理解发生了什么:

let extract1 (ids:string[]) (info:string[][]) =
    [|
        for id in ids ->
            (id, [| for record in info do if record.[1] = id then yield record.[0] |])
    |]

我的感觉,阅读您的代码,是这样的:给定一个“记录”数组 - 字段 0 中包含感兴趣的内容(可能是出版物?)的数组和字段 1 中的作者,目标是给定一组作者 ID,以提取他们的出版物。或类似的东西。

现在这不是很漂亮。另外,我不知道这是否有任何好处,所以让我们做一个基准测试 - 一个包含 1,000,000 条记录的数据集,看起来就像我认为的那样:

let ids = [| 1 .. 100 |] |> Array.map string 
let rng = System.Random()
let dataset = 
    [| for i in 0 .. 1000000 -> 
        [| System.Guid.NewGuid() |> string; rng.Next(0,100) |> string |] |]

在 FSI 中运行它会给我:

> extract1 ids dataset |> ignore;;
Real: 00:00:01.486, CPU: 00:00:01.484, GC gen0: 3, gen1: 3, gen2: 0
val it : unit = ()

我们可以让它更漂亮,或更实用吗?让我们试试吧:

let extract2 (ids:string[]) (info:string[][]) =
    ids 
    |> Seq.map (fun id -> 
        id,
        info 
        |> Seq.filter (fun record -> record.[1] = id)
        |> Seq.map (fun record -> record.[0])
        |> Seq.toArray)
    |> Seq.toArray

判决?

> extract2 ids dataset |> ignore;;
Real: 00:00:01.588, CPU: 00:00:01.593, GC gen0: 3, gen1: 3, gen2: 0
val it : unit = ()

更漂亮,但不是更好。也许问题是我们正在执行多次传递,每个 id 一次。听起来我们应该分组吗?

let extract3 (ids:string[]) (info:string[][]) =
    let IDs = ids |> Set.ofArray
    info
    |> Seq.groupBy (fun row -> row.[1])
    |> Seq.filter (fun (id,rows) -> IDs |> Set.contains id)
    |> Seq.map (fun (id,rows) -> id, rows |> Seq.toArray)
    |> Seq.toArray

> extract3 ids dataset |> ignore;;
Real: 00:00:00.387, CPU: 00:00:00.390, GC gen0: 8, gen1: 8, gen2: 0
val it : unit = ()

现在我们正在谈论。我相信我们可以挤压更多 - 随意去吧。不过,主要的一点是代码也更简单(IMO),并且更清楚地传达了意图是什么。希望这会有所帮助!

【讨论】:

    【解决方案2】:

    当试图提高一段代码的性能时,首先要做的是消除不必要的内存分配。有几个地方可以改进:

    1. 在数组上映射比在列表上映射更快,但不是 比转换为数组、映射然后再转换回来更快 到一个列表。

    2. 避免使用Array.append。这导致每次都进行新的内存分配。相反,您可以考虑使用ResizeArray 或列表,其中追加成本要低得多。

    3. 尝试使您的函数尾递归。在您的情况下,这意味着您需要将中间结果传递到下一个递归级别。在您的情况下(如果我理解正确的话),这意味着将muro 传递到调用链中,然后将该调用的结果添加到muro,然后再将其传递给下一个递归。

    尾递归函数看起来像这样:

    let rec Tuple (x:int) (ID:string list) (Information:string [] list) (result:*correct type here*) =
        if x < ID.Length then  
            let muro =  [|(ID.[x], Information|> List.filter (fun elem -> elem.[1] = humanID.[x] )|> publication 0   |> List.toArray )|]
            Tuple (x+1) ID Information Array.append muro rest     
        else  result
    let FinalTuple= Tuple 0 ID Information [||]
    

    这可能会更改结果数组的顺序,因此根据您的需要,您可能需要对其进行一些修改。

    【讨论】:

    • 你介意帮我构建尾递归函数吗?试了几天还是不行:(
    • 我添加了部分返工,应该足以让您找到正确的答案。它未经测试,因此您可能需要修复一些编译错误。
    • 我修复了复杂错误和结果数组的顺序,但仍然需要 12 秒才能解决:( 我搞砸了
    • 我的尾递归建议并没有消除Array.append。我建议将其放入列表中,然后添加到列表中。这应该会大大减少内存分配。
    • @Mathias Heh。你做了我今天懒得做的事。
    猜你喜欢
    • 2013-12-08
    • 1970-01-01
    • 2014-12-23
    • 2016-02-17
    • 1970-01-01
    • 1970-01-01
    • 2023-02-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多