Pull to refresh
0

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

Reading time3 min
Views11K
Это — вольный перевод моего недавнего поста на английской версии 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:
Total votes 38: ↑33 and ↓5+28
Comments21

Articles

Information

Website
www.intel.ru
Registered
Founded
Employees
5,001–10,000 employees
Location
США
Representative
Анастасия Казантаева