Проблема с производительностью обработки изображений F #

В настоящее время я пытаюсь улучшить производительность программы на F #, чтобы сделать ее такой же быстрой, как ее эквивалент на C #. Программа действительно применяет массив фильтров к буферу пикселей. Доступ к памяти всегда осуществляется с помощью указателей.

Вот код C #, который применяется к каждому пикселю изображения:

unsafe private static byte getPixelValue(byte* buffer, double* filter, int filterLength, double filterSum)
{
    double sum = 0.0;
    for (int i = 0; i < filterLength; ++i)
    {
        sum += (*buffer) * (*filter);
        ++buffer;
        ++filter;
    }

    sum = sum / filterSum;

    if (sum > 255) return 255;
    if (sum < 0) return 0;
    return (byte) sum;
}

Код F # выглядит так и занимает в три раза больше времени, чем программа C #:

let getPixelValue (buffer:nativeptr<byte>) (filterData:nativeptr<float>) filterLength filterSum : byte =

    let rec accumulatePixel (acc:float) (buffer:nativeptr<byte>) (filter:nativeptr<float>) i = 
        if i > 0 then
            let newAcc = acc + (float (NativePtr.read buffer) * (NativePtr.read filter))
            accumulatePixel newAcc (NativePtr.add buffer 1) (NativePtr.add filter 1) (i-1)
        else
            acc

    let acc = (accumulatePixel 0.0 buffer filterData filterLength) / filterSum                

    match acc with
        | _ when acc > 255.0 -> 255uy
        | _ when acc < 0.0 -> 0uy
        | _ -> byte acc

Использование изменяемых переменных и цикла for в F # приводит к той же скорости, что и использование рекурсии. Все проекты настроены для работы в режиме выпуска с включенной оптимизацией кода.

Как можно улучшить производительность версии F #?

РЕДАКТИРОВАТЬ:

Узкое место, по-видимому, находится в (NativePtr.get смещение буфера) . Если я заменю этот код фиксированным значением, а также заменю соответствующий код в версии C # фиксированным значением, я получу примерно одинаковую скорость для обеих программ. На самом деле, в C # скорость вообще не меняется, но в F # она имеет огромное значение.

Можно ли изменить это поведение или оно глубоко укоренилось в архитектуре F #?

РЕДАКТИРОВАТЬ 2:

Я снова реорганизовал код для использования циклов for. Скорость выполнения остается прежней:

let mutable acc <- 0.0
let mutable f <- filterData
let mutable b <- tBuffer
for i in 1 .. filter.FilterLength do
    acc <- acc + (float (NativePtr.read b)) * (NativePtr.read f)
    f <- NativePtr.add f 1
    b <- NativePtr.add b 1

Если я сравниваю код IL версии, которая использует (NativePtr.read b) , и другой версии, которая такая же, за исключением того, что в ней используется фиксированное значение 111uy вместо чтения из указателя. В коде IL меняются только следующие строки:

111uy имеет код IL ldc.i4.s 0x6f (0,3 секунды)

(NativePtr.read b) имеет строки кода IL ldloc.sb и ldobj uint8 (1,4 секунды)

Для сравнения: C # выполняет фильтрацию за 0,4 секунд.

Тот факт, что чтение фильтра не влияет на производительность, а чтение из буфера изображения, в некоторой степени сбивает с толку. Прежде чем фильтровать строку изображения, я копирую строку в буфер, имеющий длину строки. Вот почему операции чтения не распределяются по всему изображению, а находятся в этом буфере, размер которого составляет около 800 байт.

9
задан Adrian H. 20 January 2011 в 15:46
поделиться