Подтвердить что ты не робот

Авто-векторизация: Убеждение компилятора в том, что проверка псевдонима не нужна

Я занимаюсь обработкой изображений, для которых я получаю выгоду от векторизации. У меня есть функция, которая векторизовывает ok, но для которой я не могу убедить компилятор, что входной и выходной буфер не перекрывают друг друга, поэтому проверка псевдонимов не требуется. Я должен быть в состоянии сделать это с помощью __restrict__, но если буферы не определены как __restrict__ при приеме в качестве аргумента функции, невозможно убедить компилятор, что я абсолютно уверен, что два буфера никогда не будут перекрываться.

Это функция:

__attribute__((optimize("tree-vectorize","tree-vectorizer-verbose=6")))
void threshold(const cv::Mat& inputRoi, cv::Mat& outputRoi, const unsigned char th) {

    const int height = inputRoi.rows;
    const int width = inputRoi.cols;

    for (int j = 0; j < height; j++) {
        const uint8_t* __restrict in = (const uint8_t* __restrict) inputRoi.ptr(j);
        uint8_t* __restrict out = (uint8_t* __restrict) outputRoi.ptr(j);
        for (int i = 0; i < width; i++) {
           out[i] = (in[i] < valueTh) ? 255 : 0;
        }
    }
}

Единственный способ, с помощью которого я могу убедить компилятор не выполнять проверку псевдонимов, - это включить внутренний цикл в отдельную функцию, в которой указатели определяются как аргументы __restrict__. Если я объявляю эту внутреннюю функцию вложенной, снова активируется проверка псевдонимов.

Вы также можете увидеть эффект этого примера, который, как мне кажется, согласуется: http://goo.gl/7HK5p7

(Примечание: я знаю, что могут быть лучшие способы написания одной и той же функции, но в этом случае я просто пытаюсь понять, как избежать проверки псевдонимов)

Edit:
Проблема решена!! (См. ответ ниже)
Используя gcc 4.9.2, вот полный пример. Обратите внимание на использование флага компилятора -fopt-info-vec-optimized вместо замененного -ftree-vectorizer-verbose=N.
Итак, для gcc используйте #pragma GCC ivdep и наслаждайтесь!:)

4b9b3361

Ответ 1

если вы используете компилятор Intel, вы можете попробовать включить строку:

#pragma ivdep 

Следующий параграф цитируется в руководстве пользователя компилятора Intel:

Прагма ivdep указывает компилятору игнорировать принятый вектор зависимостей. Чтобы обеспечить правильный код, компилятор рассматривает предполагаемый зависимость как доказанная зависимость, которая препятствует векторизации. Эта прагма переопределяет это решение. Используйте эту прагму только тогда, когда вы знаете что предполагаемые зависимости цикла безопасны для игнорирования.

В gcc следует добавить строку:

#pragma GCC ivdep

внутри функции и прямо перед циклом, который вы хотите векторизовать (см. документация). Это поддерживается только с gcc 4.9 и, кстати, делает использование __restrict__ избыточным.

Ответ 2

Другим подходом к этой конкретной проблеме, который является стандартизованным и полностью переносимым в (разумно современном) компиляторе, является использование директивы OpenMP simd, который является частью стандарта с версии 4.0. Затем код будет выглядеть следующим образом:

void threshold(const unsigned char* inputRoi, const unsigned char valueTh,
               unsigned char* outputRoi, const int width,
               const int stride, const int height) {
    #pragma omp simd
    for (int i = 0; i < width; i++) {
        outputRoi[i] = (inputRoi[i] < valueTh) ? 255 : 0;
    }
}

И при компиляции с поддержкой OpenMP (с полной поддержкой или только с частичной только для simd, как, например, с -qopenmp-simd для компилятора Intel), код полностью векторизован.

Кроме того, это дает вам возможность указать возможное выравнивание векторов, которое может пригодиться при некоторых обстоятельствах. Например, , чтобы ваши входные и выходные массивы были выделены с помощью ассистента памяти с поддержкой выравнивания, такого как posix_memalign() с требованием выравнивания 256b, тогда код может стать:

void threshold(const unsigned char* inputRoi, const unsigned char valueTh,
               unsigned char* outputRoi, const int width,
               const int stride, const int height) {
    #pragma omp simd aligned(inputRoi, outputRoi : 32)
    for (int i = 0; i < width; i++) {
        outputRoi[i] = (inputRoi[i] < valueTh) ? 255 : 0;
    }
}

Затем это должно позволить генерировать еще более быстрый двоичный файл. И эта функция недоступна с помощью директив ivdep. Все больше причин использовать директиву OpenMP simd.

Ответ 3

Компилятор Intel по крайней мере с версии 14 не генерирует проверки псевдонимов для threshold2 в коде, который вы указали, что указывает на то, что ваш подход должен работать. Однако gcc-auto-vectorizer пропускает эту возможность для оптимизации, но генерирует векторизованный код, тесты для правильного выравнивания, тесты для псевдонимов и неэкранированный код возврата/очистки.