使用Array.Parallel.map减少运行时间

时间:2010-11-15 13:08:36

标签: f# parallel-processing

大家好

我已将C#中的项目转换为绘制Mandelbrot集的F# 不幸的是,渲染全屏需要大约一分钟,所以我试着找到一些方法来加快它。

几乎所有时间都是一个电话:

Array.map (fun x -> this.colorArray.[CalcZ x]) xyArray

xyArray (double * double) [] => (双元组的数组)
colorArray是int32 length = 255

的数组

CalcZ定义为:

 let CalcZ (coord:double * double) =

    let maxIterations = 255

    let rec CalcZHelper (xCoord:double) (yCoord:double) // line break inserted
           (x:double) (y:double) iters =
        let newx = x * x + xCoord - y * y
        let newy = 2.0 * x * y + yCoord
        match newx, newy, iters with
        | _ when Math.Abs newx > 2.0 -> iters
        | _ when Math.Abs newy > 2.0 -> iters
        | _ when iters = maxIterations -> iters
        | _ -> CalcZHelper xCoord yCoord newx newy (iters + 1)

    CalcZHelper (fst coord) (snd coord) (fst coord) (snd coord) 0

因为我只使用大约一半的处理器容量是使用更多线程的想法,特别是Array.Parallel.map,转换为system.threading.tasks.parallel

现在我的问题

天真的解决方案是:

Array.Parallel.map (fun x -> this.colorArray.[CalcZ x]) xyArray  

但这花费了两倍的时间,我怎样才能重写这个以减少时间,或者我可以采取其他方式更好地利用处理器?

提前致谢
戈尔根

--- ---编辑
调用CalcZ的函数如下所示:

          let GetMatrix =
            let halfX = double bitmap.PixelWidth * scale / 2.0
            let halfY = double bitmap.PixelHeight * scale / 2.0
            let rect:Mandelbrot.Rectangle = 
                {xMax = centerX + halfX; xMin = centerX - halfX;
                 yMax = centerY + halfY; yMin = centerY - halfY;}

            let size:Mandelbrot.Size = 
                {x = bitmap.PixelWidth; y = bitmap.PixelHeight}

            let xyList = GenerateXYTuple rect size
            let xyArray = Array.ofList xyList
            Array.map (fun x -> this.colorArray.[CalcZ x]) xyArray

        let region:Int32Rect = new Int32Rect(0,0,bitmap.PixelWidth,bitmap.PixelHeight)
        bitmap.WritePixels(region, GetMatrix, bitmap.PixelWidth * 4, region.X, region.Y);

GenerateXYTuple:

let GenerateXYTuple (rect:Rectangle) (pixels:Size) =
    let xStep = (rect.xMax - rect.xMin)/double pixels.x
    let yStep = (rect.yMax - rect.yMin)/double pixels.y
    [for column in 0..pixels.y - 1 do
       for row in 0..pixels.x - 1 do
         yield (rect.xMin + xStep * double row,
           rect.yMax - yStep * double column)]

---编辑---

根据kvb的建议(非常感谢!)在我的问题评论中,我在发布模式下构建了程序。在Relase模式下构建通常可以加快速度。

刚刚在Release中构建时,我从50s开始到30s左右,在阵列上的所有变换中移动,所以这一切都在一次传递中发生,使得它快10秒左右。最后使用Array.Parallel.init让我超过11秒。

我从中学到的是......在计时和使用并行结构时使用发布模式...... 再一次,感谢我收到的帮助。
- 编辑 -
通过使用来自本机dll的SSE声明,我已经能够将时间从大约12秒缩短到1.2秒,以获得最大计算密集点的全屏。不幸的是我没有图形处理器......

Gorgen

3 个答案:

答案 0 :(得分:3)

根据原帖的评论,这是我为测试函数而编写的代码。快速版本在我的普通工作站上只需要几秒钟。它是完全顺序的,没有并行代码。

它适度长,所以我将其发布在另一个网站上:http://pastebin.com/Rjj8EzCA

我怀疑你看到的减速是在渲染代码中。

答案 1 :(得分:1)

顺便说一下,看起来你正在生成一个坐标数组,然后将它映射到一个结果数组。如果使用init函数而不是map,则无需创建坐标数组:Array.Parallel.init 1000 (fun y -> Array.init 1000 (fun x -> this.colorArray.[CalcZ (x, y)]))

编辑:以下内容可能不准确: 你的问题可能是你召唤一个微小的函数一百万次,导致调度开销压倒你正在做的实际工作。您应该将数组划分为更大的块,以便每个单独的任务花费大约一毫秒左右。您可以使用数组数组,以便在外部数组上调用Array.Parallel.map,在内部数组上调用Array.map。这样,每个并行操作将对整行像素进行操作,而不仅仅是单个像素。

答案 2 :(得分:1)

我不认为Array.Parallel.map函数(在封面下使用.NET 4.0中的Parallel.For)如果它运行一个简单的函数〜100万次就应该无法并行操作。但是,当F#没有优化对lambda函数的调用时(在某种程度上),我在类似的情况下遇到了一些奇怪的性能行为。

我尝试从F#源获取Parallel.map函数的副本并添加inline。尝试将以下map函数添加到您的代码中并使用它而不是F#库中的函数:

let inline map (f: 'T -> 'U) (array : 'T[]) : 'U[]=
  let inputLength = array.Length
  let result = Array.zeroCreate inputLength
  Parallel.For(0, inputLength, fun i ->
    result.[i] <- f array.[i]) |> ignore
  result