GCC генерирует совершенно другой код, используя -march = native на аналогичных архитектурах.

Я работаю над написанием теста OpenCL на C. В настоящее время он измеряет производительность слитного умножения-накопления как устройства CL, так и системного процессора, используя код C. Затем результаты проходят перекрестную проверку на точность.

Я написал собственный код, чтобы воспользоваться автоматическим векторизатором GCC, и он работает. Однако я заметил, что GCC ведет себя странно с флагом «-march = native».

Это мой цикл:

#define BUFFER_SIZE_SQRT 4096
#define SQUARE(n) (n * n)

#define ROUNDS_PER_ITERATION 48

static float* cpu_result_matrix(const float* a, const float* b, const float* c)
{
    float* res = aligned_alloc(16, SQUARE(BUFFER_SIZE_SQRT) * sizeof(float));

    const unsigned buff_size = SQUARE(BUFFER_SIZE_SQRT);
    const unsigned round_cnt = ROUNDS_PER_ITERATION;

    float lres;
    for(unsigned i = 0; i < buff_size; i++)
    {
        lres = 0;
        for(unsigned j = 0; j < round_cnt; j++)
        {
            lres += a[i] * ((b[i] * c[i]) + b[i]);
            lres += b[i] * ((c[i] * a[i]) + c[i]);
            lres += c[i] * ((a[i] * b[i]) + a[i]);
        }

        res[i] = lres;
    }

    return res;
}

Когда я компилирую с помощью "-march = native -Ofast" в системе Broadwell, я получаю хороший векторизованный код AVX.

.L19:
        vmovups ymm0, YMMWORD PTR [rcx+rdx]
        mov     eax, 48
        vmovups ymm2, YMMWORD PTR [rdi+rdx]
        vaddps  ymm1, ymm0, ymm5
        vmovups ymm3, YMMWORD PTR [rsi+rdx]
        vaddps  ymm4, ymm2, ymm5
        vmulps  ymm1, ymm1, ymm2
        vfmadd132ps     ymm4, ymm1, ymm0
        vaddps  ymm1, ymm3, ymm5
        vmulps  ymm0, ymm2, ymm0
        vmulps  ymm0, ymm0, ymm1
        vfmadd132ps     ymm4, ymm0, ymm3
        vmovaps ymm1, ymm4
        vxorps  xmm0, xmm0, xmm0
        .p2align 4,,10
        .p2align 3

Компиляция с теми же флагами в системе Piledriver испускает инструкции SSE2, но не инструкции AVX, даже если архитектура поддерживает это. (Я поясню свой заголовок, сказав, что Broadwell и Piledriver не похожи друг на друга, но оба они поддерживают одинаковые расширения набора векторных инструкций, поэтому генерируемый код должен быть похожим.)

.L19:
        mov     eax, 48
        movups  xmm0, XMMWORD PTR [rcx+rdx]
        movups  xmm2, XMMWORD PTR [r13+0+rdx]
        movaps  xmm4, xmm0
        movaps  xmm1, xmm2
        movups  xmm3, XMMWORD PTR [rsi+rdx]
        addps   xmm4, xmm5
        addps   xmm1, xmm5
        mulps   xmm4, xmm2
        mulps   xmm1, xmm0
        mulps   xmm0, xmm2
        addps   xmm1, xmm4
        movaps  xmm4, xmm1
        mulps   xmm4, xmm3
        addps   xmm3, xmm5
        mulps   xmm0, xmm3
        addps   xmm4, xmm0
        pxor    xmm0, xmm0
        movaps  xmm1, xmm4
        .p2align 4,,10
        .p2align 3

Я даже могу скомпилировать весь проект с помощью -march = broadwell и запустить его в системе Piledriver, и он работает с ~ 100% приростом производительности.

Я компилирую с GCC 5.1.0, и "-ftree-vectorizer-verbose", похоже, больше не работает, поэтому поведение компилятора довольно непрозрачно. Я не нашел никакой информации об устаревшем флаге, поэтому я не уверен, почему он больше не работает, и мне действительно хотелось бы выяснить, что делает GCC.

Весь проект находится здесь: https://github.com/jakogut/clperf/tree/v0.1


person jakogut    schedule 18.06.2015    source источник
comment
Вы пытались -v увидеть, до чего -march=native расширяется? См. -fopt-info-... в документе.   -  person Marc Glisse    schedule 18.06.2015
comment
-ftree-vectorizer-verbose был заменен на -fopt-info-vec-*   -  person Ross Ridge    schedule 18.06.2015


Ответы (2)


AVX отключен, потому что все семейство AMD Bulldozer не может эффективно обрабатывать 256-битные инструкции AVX. Внутренне исполнительные блоки имеют ширину всего 128 бит. Таким образом, 256-битные операции разделяются, что не дает преимуществ перед 128-битными.

Чтобы добавить оскорбления к травме, в Piledriver есть ошибка в 256-битном хранилище, которая снижает пропускную способность до примерно 1 каждые 17 циклов..


Ваш тестовый пример кажется аномалией. В этом критическом цикле нет 256-битных хранилищ, что позволяет избежать ошибки. Это (теоретически) оставляет SSE на одном уровне с AVX для Piledriver.

Решающий момент связан с инструкциями FMA3, которые поддерживает Piledriver. Вероятно, поэтому цикл AVX действительно стал быстрее на Piledriver.

Вы можете попробовать -mfma4 -mtune=bdver2 и посмотреть, что произойдет.

person Mysticial    schedule 18.06.2015
comment
Но я думаю, что из-за общих 128-битных блоков 256-битные операции могут заимствовать исполнительный блок из другого ядра в модуле, поэтому в этом случае это более эффективно. Я считаю, что AMD даже рекламировала это (Flex FP) - person Jester; 18.06.2015
comment
Он имеет два 128-битных конвейера. Таким образом, в каждом цикле он может обрабатывать либо 2 x 128-битные инструкции, либо 1 x 256-битные. Пропускная способность такая же. Для сравнения: у процессоров Intel со времен Sandy Bridge было 2 256-битных канала. - person Mysticial; 18.06.2015
comment
Наличие двух инструкций для одинаковой производительности нехорошо, так как требует дополнительных ресурсов, таких как кеш и регистры (поскольку вы не можете адресовать верхнюю половину 256-битного регистра). - person Jester; 18.06.2015
comment
В своем руководстве по микроархитектуре Энгер пишет: «Пропускная способность 256-битных инструкций сохранения составляет менее половины пропускной способности 128-битных инструкций хранения в Bulldozer и Piledriver». Это особенно плохо для Piledriver, у которого пропускная способность составляет одно 256-битное хранилище за 17-20 тактов. Так что, если я правильно прочитал, использование 256-битных хранилищ даже на Bulldozer все равно хуже, чем два 128-битных хранилища. Но он не упоминает Steamroller, так что я думаю, что на Steamroller все в порядке. - person Z boson; 19.06.2015
comment
Я думаю, вы имеете в виду -mfma4, а не -march=fma4. - person Z boson; 19.06.2015
comment
Не было бы лучшим решением для GCC использовать AVX, но не выполнять 256-битные хранилища, вместо этого разделяя регистр AVX на высокий и низкий и выполняя два 128-битных хранилища? (Возможно, загрузка также является проблемой, но тогда можно было бы использовать две 128-битные загрузки для заполнения одного регистра AVX). - person Z boson; 19.06.2015
comment
@Zboson Если бы я написал это вручную, я бы полностью использовал 128-битные инструкции. (включая 128-битную FMA4) Это самый надежный способ избежать всех выбоин. У 256-битного больше проблем, чем просто с магазинами. reg-reg ходы не удаляются с переименованием. Перекрестки переулков - это адский беспредел ... - person Mysticial; 19.06.2015
comment
Это хорошие моменты, и я бы сделал то же самое, я думаю, но как насчет @ Jester's в отношении этого? - person Z boson; 19.06.2015
comment
К вашему сведению, я протестировал упомянутые магазины, и действительно, они чертовски медленные. Однако их можно легко эмулировать с помощью двух vextractf128 инструкций, которые работают так же быстро, как и обычные 128-битные аналоги. Однако я не пытался придумать тестовый пример, который показал бы преимущество 256-битной версии. - person Jester; 19.06.2015
comment
@Jester Я где-то читал, что единственный раз, когда Агнер Фог получил 256-битную версию быстрее, чем 128-битную, был тогда, когда код был ограничен пропускной способностью декодера. Это вряд ли произойдет с арифметикой с плавающей запятой. - person Mysticial; 19.06.2015
comment
GCC имеет параметры -mavx256-split-unaligned-store для разделения невыровненных 256-битных хранилищ на 128-битные VMOVUPS + VEXTRACTF128. Кроме того, есть -mprefer-avx128 использовать AVX128 в авто-векторизаторе. - person Marat Dukhan; 20.06.2015

Вывод «-march = native -Q --help = target» показывает, что флаги AVX и AVX2 не включены по умолчанию в архитектуре Piledriver (bdver2).

Бродвелл:

  -mavx                                 [enabled]
  -mavx2                                [enabled]

Копер:

  -mavx                                 [disabled]
  -mavx2                                [disabled]
person jakogut    schedule 18.06.2015
comment
Интересно, что debian gcc версии 4.7.2-5 включает avx и генерирует такой код, хотя и не векторизован (в основном используются варианты ss). - person Jester; 18.06.2015
comment
PS: обратите внимание, что avx2 отключен по какой-то причине, так как piledriver не выполняет avx2 :( - person Jester; 18.06.2015
comment
AVX отключен, потому что все семейство Bulldozer не запускает AVX эффективно. (не быстрее, чем SSE) Кроме того, в Piledriver есть ошибка в 256-битном хранилище, которая делает AVX почти полностью бесполезным. Ваш случай - аномалия, потому что у вас нет 256-битных хранилищ, и FMA3, вероятно, станет плюсом. - person Mysticial; 18.06.2015
comment
Это именно то, что я искал, Mysticial. Спасибо за объяснение! - person jakogut; 18.06.2015
comment
@Mysticial, который кажется ответом, это очень полезная информация, мне не нравится важная информация в комментариях, потому что я видел, как исчезают полезные комментарии, и это никому не помогает в долгосрочной перспективе. - person Shafik Yaghmour; 18.06.2015
comment
@ShafikYaghmour Готово. :) - person Mysticial; 18.06.2015