используя Array.Parallel.map для уменьшения времени выполнения - PullRequest
5 голосов
/ 15 ноября 2010

Привет всем

Я преобразовал проект в C # в F #, который рисует набор Мандельброта.
К сожалению, для рендеринга в полноэкранном режиме требуется около одной минуты, поэтому я пытаюсь найти некоторые способыускорить его.

Это один вызов, который занимает почти все время:

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

xyArray (double * double) [] => (массив кортежа double)
colorArray - массивдлина int32 = 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.карта, переводится как system.threading.tasks.parallel

Теперь мой вопрос

Наивное решение, будет:

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

но это заняло вдвое больше времени, как я могу переписать это, чтобы оно заняло меньше времени, или я могу использовать другой способ, чтобы лучше использовать процессор?

Заранее спасибо
Gorgen

--- edit ---
функция, которая вызывает 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)]

--- edit ---

По предложению от kvb (спасибо большое!) Вкомментарий к моему вопросу, я построил программу в режиме выпуска.Сборка в режиме Relase обычно ускоряет процесс.

Простое встраивание в релиз заняло у меня от 50 до примерно 30 секунд, когда все преобразования в массиве выполнялись так, что все это происходит за один проход, и это на 10 секунд быстрее.Наконец использование Array.Parallel.init дало мне чуть более 11 секунд.

Из этого я узнал, что ... Используйте режим освобождения при синхронизации событий и использовании параллельных конструкций ... Еще раз, спасибо за помощь, которую я получил.
-edit -
с помощью SSE-ассемблера из нативной dll. Мне удалось сократить время с 12 секунд до 1,2 секунд для полного экрана наиболее интенсивных вычислительных точек.К сожалению, у меня нет графического процессора ...

Gorgen

Ответы [ 3 ]

3 голосов
/ 17 ноября 2010

В комментарии к исходному сообщению приведен код, который я написал для проверки функции.Быстрая версия занимает всего несколько секунд на моей средней рабочей станции.Он полностью последовательный и не имеет параллельного кода.

Он умеренно длинный, поэтому я разместил его на другом сайте: http://pastebin.com/Rjj8EzCA

Я подозреваю, что наблюдаемое замедлениекод рендеринга.

1 голос
/ 15 ноября 2010

Я не думаю, что функция Array.Parallel.map (которая использует Parallel.For из .NET 4.0 под прикрытием) должна иметь проблемы с распараллеливанием операции, если она выполняет простую функцию ~ 1 миллион раз. Однако я столкнулся с некоторым странным поведением производительности в подобном случае, когда F # не оптимизировал вызов лямбда-функции (каким-то образом).

Я бы попробовал взять копию функции Parallel.map из источников F # и добавить 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
1 голос
/ 15 ноября 2010

Кроме того, похоже, что вы генерируете массив координат, а затем сопоставляете его с массивом результатов.Вам не нужно создавать массив координат, если вы используете функцию init вместо map: Array.Parallel.init 1000 (fun y -> Array.init 1000 (fun x -> this.colorArray.[CalcZ (x, y)]))

РЕДАКТИРОВАТЬ: следующее может быть неточным: Возможно, ваша проблема заключается в том, что вывызовите крошечную функцию миллион раз, что приведет к тому, что затраты на планирование сократят реальную работу, которую вы выполняете.Вы должны разбить массив на гораздо большие куски, чтобы каждая отдельная задача занимала миллисекунду или около того.Вы можете использовать массив массивов, чтобы вызывать Array.Parallel.map для внешних массивов и Array.map для внутренних массивов.Таким образом, каждая параллельная операция будет работать с целым рядом пикселей вместо одного пикселя.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...