Мотивирующий пример
Представьте, что мы хотим размыть все строки изображения путем свертки каждой строки с помощью хорошо известного ядра фильтра {1, 4, 6, 4, 1. Первые и последние два пикселя каждой строки должны обрабатываться отдельно, поскольку в противном случае мы получили бы доступ к пикселям, выходящим за пределы поля. Вот диаграмма:
К сожалению, эти граничные проверки препятствуют оптимизации компилятора, поэтому вместо написания одного цикла с граничными проверками (см. Blur_rows1 ниже для справки), нам приходится разделить цикл на три отдельных цикла (см. функцию Blur_rows2):
- один цикл над первыми двумя пикселями,
- один цикл над средними пикселями который выполняет большую часть работы и не требует каких-либо проверок границ, и
- один цикл по последним двум пикселям:
for (int x = 0; x < radius; x++) blur(dst, src, nx, ny, x, y, kernel, radius);
for (int x = radius; x < nx - radius; x++) blur(dst, src, nx, ny, x, y, kernel, radius);
for (int x = nx - radius; x < nx ; x++) blur(dst, src, nx, ny, x, y, kernel, radius);
Это делает Blur_rows2 примерно в 10 раз быстрее, чем Blur_rows1 (50 мкс против 500 мкс для изображения 512x512).
Ошибочные решения
Существует несколько способов реализации разделения цикла, каждый из которых имеет различные недостатки:- Реализация внутреннего -большинство тело цикла как функцию, как показано ниже. Недостатки:
- Невозможно прочитать код сверху вниз, поскольку внутренний цикл реализован как функция где-то в другом месте (плохая локальность кода).
< li>Безумно большие списки параметров. - Встраивает функцию в компилятор (например, __attribute__((always_inline)) с помощью GCC)
- Опирается на компилятору, чтобы распознать, что граничные проверки могут быть удалены в среднем цикле.
- Невозможно прочитать код сверху вниз, поскольку внутренний цикл реализован как функция где-то в другом месте (плохая локальность кода).
- Реализуйте самое внутреннее тело цикла как макрос.
- Также страдает от недостаточной локальности кода.
- Также приходится полагаться на оптимизацию компилятора.
< li>Необходимо добавлять \ после каждой строки. - обычные недостатки макросов (ошибки компилятора становятся непонятными, код становится сложнее читать и труднее рассуждать).
- Дополните src значениями заполнения границ, чтобы проверка границ не требовалась.
- Повышает требования к памяти.
- Повышает затраты на пропускную способность памяти. .
- https://github.com/halide/Halide
- Сложно учиться.
- Редко компилируется.
Вопрос
Было бы неплохо, если бы компилятор поддерживал что-то вроде разделения цикла #pragma. (0, radius, nx - radius, nx) или, что еще лучше, какой-то способ сообщить компилятору, что он должен разбить цикл так, чтобы выполнялось условие if (0Подробнее здесь: https://stackoverflow.com/questions/793 ... -splitting
Мобильная версия