company_banner

Новая библиотека x86 SIMD интринсиков — immintrin debug

    С каждым новым поколением процессоров Intel появляются новые и все более сложные векторные инструкции. Хотя длина вектора (512 бит) в ближайшее время расти не будет, появятся новые типы данных и виды инструкций. Например, кто сможет с первого взгляда понять, что делает такой интринсик (и соответствующая ему инструкция процессора)?

    Bitwise ternary logic that provides the capability to implement any three-operand binary function; the specific binary function is specified by value in imm8.

    __m512i _mm512_mask_ternarylogic_epi32 (__m512i src, __mmask8 k, __m512i a, __m512i b, int imm8)
    FOR j := 0 to 15
        i := j*32
        IF k[j]
            FOR h := 0 to 31
                index[2:0] := (src[i+h] << 2) OR (a[i+h] << 1) OR b[i+h]
                dst[i+h]   := imm8[index[2:0]]
            ENDFOR
        ELSE
            dst[i+31:i] := src[i+31:i]
        FI
    ENDFOR
    dst[MAX:512] := 0
    

    ОК, допустим, мы разобрались, как она работает. Следующий уровень сложности — отладка кода, интенсивно использующего такие интринсики.

    Те, кто регулярно пользуются интринсиками, знают такой очень полезный сайт — Intel intrinsics guide. Если внимательно посмотреть, как он устроен, то легко заметить, что javascript фронтенд скачивает файл data-3.x.x.xml, в котором подробно описаны все интринсики, с кодом, похожим на Matlab. (Например, тот, что я скопировал в заголовке поста.)

    Но когда мы используем интринсики для ускорения кода, мы пишем не на Matlab, а на С и С++! Три месяца назад один клиент спросил меня, существует ли реализация векторных интринсиков на С для отладки, и я решил написать парсер, который транслирует код из Intrinsics Guide в С. Получается библиотека, которая реализует почти все интринсики так, что пошаговым отладчиком можно заходить внутрь (или добавить отладочных printf).

    Например, операция из заголовка поста превращается в

    for (int j = 0; j <= 15; j++) {
      if (k & (1 << j)) {
        for (int h = 0; h <= 31; h++) {
          int index =  ((((src_vec[j] & (1 << h)) >> h) << 2) |
                       (((a_vec[j] & (1 << h)) >> h) << 1) |
                       ((b_vec[j] & (1 << h)) >> h)) & 0x7;
          dst_vec[j] = (dst_vec[j] & ~(1 << h)) |
                       ((((imm8 & (1 << index)) >> index)) << h);
        }
      } else {
        dst_vec[j] = src_vec[j];
      }
    }
    

    Правда, так гораздо понятнее? Не очень? Ну, это я просто сложную функцию выбрал для примера. Обычно, когда отлаживаешь код с интринсиками, (например, DSP) приходится держать в голове как алгоритм, так и особенности каждой инструкции. Учитывая, что инструкции работают с длинными векторами, а DSP алгоритмы часто еще и основаны на серьезной математике, моя голова не справляется — кратковременной памяти и концентрации не хватает. Подозреваю, что я не одинок — несколько раз я даже думал, что нашел баг в инструкции. Потом, разумеется, каждый раз оказывалось, что ошибался я, и открыть новый FDIV bug не получилось. Но если бы я мог в тех случаях пошагово отлаживать внутри инструкции, я сразу же понял бы, при каких условиях в компоненте моего вектора появляется значение, которое я не ожидал.

    Клиенты мне говорили, что используют эту библиотеку для того, чтобы отлаживать отдельные функции с AVX-512 интринсиками на лэптопе, который поддерживает только AVX2. Конечно, для этого гораздо лучше подходит Intel SDE — потому, что он предельно аккуратно имитирует все наборы инструкций. У меня есть набор юнит тестов (тоже автоматически сгенерированных), которые для каждого интринсика из библиотеки сравнивают результат его работы с результатом исполнения соответствующей ассемблерной инструкции. Как и положено юнит тестам, большинство работает как надо. Но некоторые отладочные интринсики с плавающей точкой (и двойной, и одинарной точности) не всегда работают на 100% корректно. Я бы сказал, что иногда получается эдакий -ffast-math. А еще есть разные механизмы округления! В IEE754 много тонкостей…

    Есть еще одна важная особенность использования immintrin debug вместо SDE (что я всячески не одобряю, но помешать этому не могу). Если компилировать gcc или clang с опцией, например, -march=nehalem, то gcc и clang возвращают из функций 512-битные вектора на стеке, а ICC все равно возвращает их в ZMM0. Так что компилятор Intel в этом режиме использовать не получится. А еще у gcc есть полезная опция -Og, которая помогает при отладке, в том числе с immintrin debug.

    Существует несколько интринсиков, главное действие которых — изменение содержимого регистра, например, или флагов. Такие инструкции я не реализовал. Ну и пока мой парсер не совсем готов, реализация еще примерно 10% интринсиков пока отсутствует.

    Использовать immintrin debug очень просто — исходники менять не надо, но придется добавить условную компиляцию, чтобы включать immintrin_dbg.h вместо immintrin.h в случае отладочной сборки.

    Скачать можно на гитхабе.
    Intel
    178,78
    Компания
    Поделиться публикацией

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое