Вот выдержка из рассматриваемой программы. Матрица img[][]
имеет размер SIZE × SIZE и инициализируется в:
img[j][i] = 2 * j + i
Затем вы создаете матрицу res[][]
, и каждое поле здесь считается средним из 9 полей вокруг нее в матрице img. Граница оставлена на 0 для простоты.
for(i=1;i<SIZE-1;i++)
for(j=1;j<SIZE-1;j++) {
res[j][i]=0;
for(k=-1;k<2;k++)
for(l=-1;l<2;l++)
res[j][i] += img[j+l][i+k];
res[j][i] /= 9;
}
Это все, что есть в программе. Для полноты картины вот что предшествует. Код не приходит после. Как видите, это просто инициализация.
#define SIZE 8192
float img[SIZE][SIZE]; // input image
float res[SIZE][SIZE]; //result of mean filter
int i,j,k,l;
for(i=0;i<SIZE;i++)
for(j=0;j<SIZE;j++)
img[j][i] = (2*j+i)%8196;
В основном, эта программа работает медленно, когда SIZE кратен 2048, например, время выполнения:
SIZE = 8191: 3.44 secs
SIZE = 8192: 7.20 secs
SIZE = 8193: 3.18 secs
Компилятор GCC. Из того, что я знаю, это из-за управления памятью, но я не очень много знаю об этом предмете, вот почему я спрашиваю здесь.
Также, как это исправить, было бы неплохо, но если бы кто-то мог объяснить это время выполнения, я уже был бы достаточно счастлив.
Я уже знаю о malloc / free, но проблема не в объеме используемой памяти, а в просто времени выполнения, поэтому я не знаю, как это могло бы помочь.
источник
Ответы:
Разница вызвана той же проблемой супер-выравнивания из следующих связанных вопросов:
Но это только потому, что есть еще одна проблема с кодом.
Начиная с оригинального цикла:
Сначала обратите внимание, что две внутренние петли тривиальны. Их можно развернуть следующим образом:
Так что остается два внешних цикла, которые нас интересуют.
Теперь мы видим, что проблема в этом вопросе та же: почему порядок циклов влияет на производительность при итерации по двумерному массиву?
Вы перебираете матрицу по столбцам, а не по строкам.
Чтобы решить эту проблему, вы должны поменять местами два цикла.
Это полностью исключает непоследовательный доступ, поэтому вы больше не получаете случайных замедлений при больших степенях двойки.
Core i7 920 @ 3,5 ГГц
Оригинальный код:
Взаимозаменяемые внешние петли:
источник
Следующие тесты были выполнены с компилятором Visual C ++, так как он используется при установке по умолчанию Qt Creator (я думаю, без флага оптимизации). При использовании GCC нет большой разницы между версией Mystical и моим «оптимизированным» кодом. Итак, вывод заключается в том, что оптимизация компилятора заботится о микрооптимизации лучше, чем люди (наконец-то я). Я оставляю остаток моего ответа для справки.
Неэффективно обрабатывать изображения таким образом. Лучше использовать одномерные массивы. Обработка всех пикселей выполняется за один цикл. Произвольный доступ к точкам может быть выполнен с использованием:
В этом конкретном случае лучше вычислять и кэшировать сумму трех пиксельных групп по горизонтали, поскольку они используются по три раза каждая.
Я сделал несколько тестов, и я думаю, что стоит поделиться. Каждый результат - в среднем пять тестов.
Оригинальный код от пользователя1615209:
Мистическая версия:
Два прохода с использованием одномерного массива: первый проход для горизонтальных сумм, второй для вертикальной суммы и среднего. Двухпроходная адресация с тремя указателями и только такими приращениями:
Два прохода с использованием одномерного массива и адресации следующим образом:
Горизонтальные кеширующие за один проход суммы всего на одну строку впереди, поэтому они остаются в кеше:
Вывод:
Я уверен, что можно сделать намного лучше.
ПРИМЕЧАНИЕ. Обратите внимание, что я написал этот ответ для решения общих проблем с производительностью, а не проблемы с кешем, описанной в превосходном ответе Mystical. В начале это был просто псевдокод. Меня попросили сделать тесты в комментариях ... Вот полностью переработанная версия с тестами.
источник