Pull to refresh
198.34
Rating

Помочь компилятору в векторизации? — Лучше просто не мешать

Intel corporate blog
Это — вольный перевод моего недавнего поста на английской версии Intel Software Network. Так что те, кому Victoria Zhislina нравится больше vikky13, кто уже видел этот пост, могут сразу прочесть первый и последний абзацы, отсутствующие в оригинале.

— Всем здрасьте, мне нужен транслятор с русского языка в код программы на C++. Ну то есть, я пишу задачу, а транслятор реализует ее решение на языке С++. Где можно такой найти? Если для Cи нету, может быть, есть для других языков?

— Есть, называется начальник отдела разработки. Пишешь задачу на русском — отдаешь подчиненным и все, код готов! Хоть на Си, хоть на Дельфи, хоть на Яве. Я проверял, работает!


Говорят, что это не анекдот, а реальный вопрос на программистском форуме. Также говорят, что человек гораздо умнее машины, а значит, может ей помочь — поделиться умом. Но есть немало случаев, когда делать этого точно не стоит. Результат будет обратный ожидаемому.

Вот наглядный пример из известной open source библиотеки OpenCV:

cvtScale_( const Mat& srcmat, Mat& dstmat, double _scale, double _shift )
{
    Op op;
    typedef typename Op::type1 WT;
    typedef typename Op::rtype DT;
    Size size = getContinuousSize( srcmat, dstmat, srcmat.channels() );
    WT scale = saturate_cast<WT>(_scale), shift = saturate_cast<WT>(_shift);

    for( int y = 0; y < size.height; y++ )
    {
        const T* src = (const T*)(srcmat.data + srcmat.step*y);
        DT* dst = (DT*)(dstmat.data + dstmat.step*y);
        int x = 0;
        for(; x <= size.width - 4; x += 4 )
        {
            DT t0, t1;
            t0 = op(src[x]*scale + shift);
            t1 = op(src[x+1]*scale + shift);
            dst[x] = t0; dst[x+1] = t1;
            t0 = op(src[x+2]*scale + shift);
            t1 = op(src[x+3]*scale + shift);
            dst[x+2] = t0; dst[x+3] = t1;
        }
        for( ; x &lt; size.width; x++ )
            dst[x] = op(src[x]*scale + shift);

      }
}

Это — простая функция-шаблон, работающая с char, short, float и double.
Ее авторы решили помочь компилятору с SSE-векторизацией, развернув внутренний цикл по 4 и обрабатывая оставшийся хвост данных отдельно.
Думаете, современные компиляторы (под Windows) сгенеруют оптимизированный код в соответствии с замыслом авторов?
Давайте проверим, скомпилировав этот код с помощью Intel Compiler 12.0, с ключом /QxSSE2 (проверено, что использование других SSEx и AVX опций даст такой же результат)

А результат будет довольно неожиданный. Ассемблерный листинг на выходе компилятора неопровержимо показывает, что развернутый цикл НЕ векторизуется. Компилятор генерирует SSE инструкции, но только скалярные, а не векторные. Зато остаток данных — «хвост», содержащий всего 1-3 элемента данных в неразвернутом цикле, векторизуется по полной программе!

Если мы уберем разворачивание цикла:

for( int y = 0; y < size.height; y++ )
    {
        const T* src = (const T*)(srcmat.data + srcmat.step*y);
        DT* dst = (DT*)(dstmat.data + dstmat.step*y);
        int x = 0;
        for( ; x < size.width; x++ )
            dst[x] = op(src[x]*scale + shift);
    }

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

Вывод: Больше работы — меньше производительность. Меньше работы — больше. Вот бы всегда так.

Заметим, что Microsoft Compiler, Visual Studio 2010 и 2008 с ключом /arch:SSE2 НЕ векторизует вышеприведенный код ни в развернутом ни в свернутом виде. Код, им произведенный, очень похож и по виду и по производительности в обоих случаях. То есть, если для компилятора Intel развертывание цикла — вредно, то для Microsoft — просто бесполезно :).

А что если вы все же хотите сохранить развертывание цикла — оно дорого вам как память, но и векторизацию тоже хотите?

Тогда используйте прагмы компилятора Intel как показано ниже:
#pragma simd

        for(x=0; x <= size.width - 4; x += 4 )
        {
            DT t0, t1;
            t0 = op(src[x]*scale + shift);
            t1 = op(src[x+1]*scale + shift);
            dst[x] = t0; dst[x+1] = t1;
            t0 = op(src[x+2]*scale + shift);
            t1 = op(src[x+3]*scale + shift);
            dst[x+2] = t0; dst[x+3] = t1;
        } 

#pragma novector

for( ; x <size.width; x++ )
     dst[x] = op(src[x]*scale + shift);
}


И последнее. Само по себе разворачивание циклов может положительно повлиять на производительность. Но, во-первых, возможный выигрыш от векторизации все равно превысит это положительное влияние, а, во-вторых, разворачивание можно поручить компилятору, тогда и векторизация от этого не пострадает. В числе прочего планирую затронуть эту тему на вебинаре 27 октября.
Tags:оптимизация программоптимизация кодавекторизациякомпилятор
Hubs: Intel corporate blog
Total votes 38: ↑33 and ↓5 +28
Views10.5K

Comments 21

Only those users with full accounts are able to leave comments. Log in, please.

Information

Founded
Location
США
Website
www.intel.ru
Employees
5,001–10,000 employees
Registered
Representative
Виктор Гурылев

Habr blog