我已将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
答案 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