Я пишу функцию для создания gaussian фильтра (используя библиотеку armadillo), которая может быть либо 2D, либо 3D в зависимости от количества измерений ввода, который он получает. Вот код:Почему я не жертва предсказания ветвей?
template <class ty>
ty gaussianFilter(const ty& input, double sigma)
{
// Our filter will be initialized to the same size as our input.
ty filter = ty(input); // Copy constructor.
uword nRows = filter.n_rows;
uword nCols = filter.n_cols;
uword nSlic = filter.n_elem/(nRows*nCols); // If 2D, nSlic == 1.
// Offsets with respect to the middle.
double rowOffset = static_cast<double>(nRows/2);
double colOffset = static_cast<double>(nCols/2);
double sliceOffset = static_cast<double>(nSlic/2);
// Counters.
double x = 0 , y = 0, z = 0;
for (uword rowIndex = 0; rowIndex < nRows; rowIndex++) {
x = static_cast<double>(rowIndex) - rowOffset;
for (uword colIndex = 0; colIndex < nCols; colIndex++) {
y = static_cast<double>(colIndex) - colOffset;
for (uword sliIndex = 0; sliIndex < nSlic; sliIndex++) {
z = static_cast<double>(sliIndex) - sliceOffset;
// If-statement inside for-loop looks terribly inefficient
// but the compiler should take care of this.
if (nSlic == 1){ // If 2D, Gauss filter for 2D.
filter(rowIndex*nCols + colIndex) = ...
}
else
{ // Gauss filter for 3D.
filter((rowIndex*nCols + colIndex)*nSlic + sliIndex) = ...
}
}
}
}
Как мы видим, есть Условный оператор внутри самого внутреннего цикла, который проверяет, если размер третьего измерения (nSlic) равна 1. После того, вычисленной в начале функции nSlic не изменит ее значение, поэтому компилятор должен быть достаточно умным, чтобы оптимизировать условную ветвь, и я не должен потерять какую-либо производительность.
Однако ... если я удалю if-инструкцию из цикла, я получаю повышение производительности.
if (nSlic == 1)
{ // Gauss filter for 2D.
for (uword rowIndex = 0; rowIndex < nRows; rowIndex++) {
x = static_cast<double>(rowIndex) - rowOffset;
for (uword colIndex = 0; colIndex < nCols; colIndex++) {
y = static_cast<double>(colIndex) - colOffset;
for (uword sliIndex = 0; sliIndex < nSlic; sliIndex++) {
z = static_cast<double>(sliIndex) - sliceOffset;
{filter(rowIndex*nCols + colIndex) = ...
}
}
}
}
else
{
for (uword rowIndex = 0; rowIndex < nRows; rowIndex++) {
x = static_cast<double>(rowIndex) - rowOffset;
for (uword colIndex = 0; colIndex < nCols; colIndex++) {
y = static_cast<double>(colIndex) - colOffset;
for (uword sliIndex = 0; sliIndex < nSlic; sliIndex++) {
z = static_cast<double>(sliIndex) - sliceOffset;
{filter((rowIndex*nCols + colIndex)*nSlic + sliIndex) = ...
}
}
}
}
После компиляции с g++ -O3 -c -o main.o main.cpp
и измерением времени выполнения обоих вариантов кода я получил следующее:
(1000 повторов, 2D матрицы размера 2048)
Если-внутри:
- 66.0453 секунд
- 64.7701 секунд
Если внешняя:
- 64.0148 секунд
- 63.6808 секунд
Почему не компилятор оптимизировать ветку, если значение nSlic даже не изменится? Мне обязательно нужно перестроить код, чтобы избежать ошибки в for
-loop?
Я смущен тем, что вы просите. Вы переместили оператор if из вложенного цикла и удивлены, что ваш код работает быстрее? Ожидаете ли вы, что компилятор преобразует вашу первую версию кода во вторую? – Kevin
Я считал, что если оператор 'if' всегда будет давать тот же результат, компилятор будет его оптимизировать. Мои предположения взяты из [отсортированного или несортированного массива] (http://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-an-unsorted-array). Я хотел бы понять, почему это не так, и когда я могу ожидать таких оптимизаций компилятора. – dangom
О, я вижу. Однако это не работа компилятора. Процессор обрабатывает предсказание ветвления. – Kevin