Проблема с производительностью обработки изображений 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) (filterData:nativeptr) filterLength filterSum : byte =

    let rec accumulatePixel (acc:float) (buffer:nativeptr) (filter:nativeptr) 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. чтение 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 байт.

Если вы опытный пользователь drawRect, то знаете, что, конечно же, drawRect не будет запускаться до тех пор, пока «вся обработка не будет завершена».

setNeedsDisplay помечает представление как недействительное и ОС, и в основном ожидает, пока вся обработка сделано. Это может приводить в ярость в обычной ситуации, когда вы хотите:

  • контроллер представления 1
  • запускает некоторую функцию 2
  • , которая постепенно 3

    Если вы опытный пользователь drawRect, то знаете, что, конечно же, drawRect не будет запускаться до тех пор, пока «вся обработка не будет завершена».

    setNeedsDisplay помечает представление как недействительное и ОС, и в основном ожидает, пока вся обработка сделано. Это может приводить в ярость в обычной ситуации, когда вы хотите:

    • контроллер представления 1
    • запускает некоторую функцию 2
    • , которая постепенно 3
      • создает все более и более сложные изображения и 4
      • на каждом шаге, вы устанавливаете NeedDisplay (неправильно!) 5
    • , пока вся работа не будет сделана 6

    Конечно, когда вы выполните указанные выше 1-6 , все, что происходит, это то, что drawRect запускается только один раз после шага 6.

    Ваша цель - обновить представление в пункте 5. Что делать?


    Решение исходного вопроса. .............................................

    В слово, вы можете (A) фон большая картина, и вызов на первый план для обновления пользовательского интерфейса или (в), возможно, спорной существует четыре «непосредственные» методы предложили не использовать фоновый процесс. Для получения результата запустите демонстрационную программу. В нем есть #defines для всех пяти методов.


    Поистине поразительное альтернативное решение, представленное Томом Свифтом ..................

    Том Свифт объяснил удивительную идею довольно простого управления циклом выполнения . Вот как запускается цикл выполнения:

    [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];

    Это поистине удивительный образец инженерной мысли. Конечно, при манипулировании циклом выполнения нужно быть предельно осторожным, и, как многие отмечали, этот подход предназначен исключительно для экспертов.


    Возникающая причудливая проблема .................. ............................

    Несмотря на то, что некоторые методы работают, на самом деле они не "работают", потому что там - странный артефакт прогрессивного замедления, который вы ясно увидите в демонстрации.

    Прокрутите до «ответа», который я вставил ниже, показывая вывод консоли - вы можете увидеть, как он постепенно замедляется.

    Вот новый Вопрос SO:
    Загадочная проблема "прогрессивного замедления" в run loop / drawRect

    Вот версия 2 демонстрационного приложения ...
    http: //www.fileswap. com / dl / p8lU3gAi / stepwiseDrawingV2.zip.html

    Вы увидите, что он тестирует все пять методов,

    #ifdef TOMSWIFTMETHOD
     [self setNeedsDisplay];
     [[NSRunLoop currentRunLoop]
          runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
    #endif
    #ifdef HOTPAW
     [self setNeedsDisplay];
     [CATransaction flush];
    #endif
    #ifdef LLOYDMETHOD
     [CATransaction begin];
     [self setNeedsDisplay];
     [CATransaction commit];
    #endif
    #ifdef DDLONG
     [self setNeedsDisplay];
     [[self layer] displayIfNeeded];
    #endif
    #ifdef BACKGROUNDMETHOD
     // here, the painting is being done in the bg, we have been
     // called here in the foreground to inval
     [self setNeedsDisplay];
    #endif
    
    • Вы сами можете убедиться, какие методы работают, а какие нет.

    • вы можете увидеть причудливую "прогрессивную- помедленнее". почему это происходит?

    • вы можете увидеть спорный метод TOMSWIFT, нет фактически никаких проблем с отзывчивостью. нажмите для ответа в любое время. (но все же странная проблема «прогрессивного замедления»)

    Итак, подавляющая вещь - это странное «прогрессивное замедление»: на каждой итерации, по неизвестным причинам, время, затрачиваемое на цикл, уменьшается. Обратите внимание, что это применимо как к выполнению «правильно» (фоновый вид), так и к использованию одного из «немедленных» методов.


    Практические решения ........................

    Для всех, кто читает в будущем, если вы действительно не можете заставить это работать в производственном коде из-за «загадочного прогрессивного замедления» ... Фелц и Войд предложили поразительные решения в другом конкретном вопросе, надеюсь, это поможет.

44
задан 26 revs, 3 users 100% 23 May 2017 в 12:34
поделиться