F#中的并行存在函数

时间:2011-12-16 14:58:15

标签: f# parallel-processing task-parallel-library

动机

我有一个长时间运行的布尔函数,应该在数组中执行,如果数组中的元素满足条件,我想立即返回。我希望并行执行搜索并在第一个完整的线程返回正确答案时终止其他线程。

问题

在F#中实现并行存在函数的好方法是什么?由于我的目标是性能,因此有效的解决方案比简单或惯用的解决方案更受欢迎。

测试用例

假设我想查找数组中是否存在一个值。并且比较函数(equals)被模拟为计算昂贵的函数:

open System.Diagnostics
open System.Threading

// Source at http://parallelpatterns.codeplex.com/releases/view/50473
let doCpuIntensiveOperation seconds (token:CancellationToken) throwOnCancel =
        if (token.IsCancellationRequested) then
            if (throwOnCancel) then token.ThrowIfCancellationRequested()
            false
        else
            let ms = int64 (seconds * 1000.0)
            let sw = new Stopwatch()
            sw.Start()
            let checkInterval = Math.Min(20000000, int (20000000.0 * seconds))

            // Loop to simulate a computationally intensive operation
            let rec loop i = 
                // Periodically check to see if the user has requested 
                // cancellation or if the time limit has passed
                let check = seconds = 0.0 || i % checkInterval = 0
                if check && token.IsCancellationRequested then
                    if throwOnCancel then token.ThrowIfCancellationRequested()
                    false
                elif check && sw.ElapsedMilliseconds > ms then
                    true
                else 
                  loop (i + 1)

            // Start the loop with 0 as the first value
            loop 0

let inline equals x y =
    doCpuIntensiveOperation 0.01 CancellationToken.None false |> ignore
    x = y

该数组由1000个随机生成的元素组成,并且在数组的后半部分保证搜索值(因此顺序搜索必须至少经过数组的一半):

let rand = new System.Random()
let m = 1000
let N = 1000000
let xs = [|for _ in 1..m -> rand.Next(N)|]
let i = rand.Next((m-1)/2, m-1);;

#time "on";;
let b1 = parallelExists (equals xs.[i]) xs;; // Parallel
let b2 = Array.exists (equals xs.[i]) xs;; // Sequential

2 个答案:

答案 0 :(得分:3)

我认为您可以采取以下步骤:

  1. 产生一些工作者(线程或异步计算),并传递每个相等的数组和一个将由所有工作人员共享的取消令牌

  2. 当工人找到搜索到的项目时,它会在令牌上调用取消(每个工作人员应在每次迭代时检查令牌的取消状态并在需要时保释)

  3. 我目前没有时间编写代码,因此我可能会忽略一些细节。

    answer及相关问题可能会有所帮助。

    更新

    这是我正在思考的一个例子

    open System
    open System.Collections.Generic
    open System.Threading
    open System.Threading.Tasks
    
    let getChunks size array =
      let rec loop s n =
        seq {
          if n > 0 then
            let r = n - size
            if r > 0 then yield (s, size); yield! loop (s + size) r
            else yield (s, size + r)
        }      
      loop 0 (Array.length array)
    
    [<Literal>]
    let CHUNK_SIZE = 3
    
    let parallelExists f (array:_[]) =
      use cts = new CancellationTokenSource()
      let rec checkSlice i n = 
        if n > 0 && not cts.IsCancellationRequested then
          if f array.[i] then cts.Cancel()
          else checkSlice (i + 1) (n - 1)
      let workers =
        array
        |> getChunks CHUNK_SIZE
        |> Seq.map (fun (s, c) -> Task.Factory.StartNew(fun () -> checkSlice s c))
        |> Seq.toArray
      try 
        Task.WaitAll(workers, cts.Token)
        false
      with :? OperationCanceledException -> true
    

    用法

    let array = Array.init 10 id
    let exists = 
      array |> parallelExists (fun i ->
        Thread.Sleep(500)
        i = 9)
    printfn "%b" exists //true
    

答案 1 :(得分:1)

F#Powerpack有PSeq.exists,映射到PLINQ的ParallelEnumerable.Any,它是BCL的一部分。还有ParallelEnumerable.First

我试图反编译,但不知道发生了什么。所以相反,我去执行以下副作用代码,以确认它在找到元素后使用某种取消:

let elems = seq {
    for x = 0 to 1000000 do
        printfn "test"
        yield x }

open System
open System.Linq;;

ParallelEnumerable.First (ParallelEnumerable.AsParallel(elems), Func<_,_>(fun x -> x = 1))