Встроенные трансляции с встроенными функциями и сборкой

В разделе 2.5.3 «Трансляции» Справочник по программированию расширений набора инструкций архитектуры Intel, что мы узнаем, чем у AVX512 (и Knights Corner) есть

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

Например, используя синтаксис сборки Intel, мы можем транслировать скаляр по адресу, хранящемуся в rax, а затем умножить его на 16 чисел с плавающей запятой в zmm2 и записать результат в zmm1 следующим образом

vmulps zmm1, zmm2, [rax] {1to16}

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

__m512 bb = _mm512_set1_ps(b);
__m512 ab = _mm512_mul_ps(a,bb);

к единой инструкции

vmulps zmm1, zmm2, [rax] {1to16}

но я не наблюдал, чтобы GCC делал это. Я нашел отчет об этой ошибке GCC.

Я наблюдал нечто подобное с FMA с GCC. например GCC 4.9 не сворачивает _mm256_add_ps(_mm256_mul_ps(areg0,breg0) до одной инструкции fma с -Ofast. Тем не менее, GCC 5.1 теперь сворачивает его до единой fma. По крайней мере, для этого с FMA есть свои особенности, например: _mm256_fmadd_ps. Но нет, например, _mm512_mulbroad_ps(vector,scalar) внутренний.

GCC может исправить это в какой-то момент, но до тех пор сборка - единственное решение.

Итак, мой вопрос: как это сделать с помощью встроенной сборки в GCC?

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

"vmulps        (%%rax)%{1to16}, %%zmm1, %%zmm2\n\t"

Я действительно ищу такую ​​функцию

static inline __m512 mul_broad(__m512 a, float b) {
    return a*b;
}

где, если b находится в памяти, указывает на rax, он производит

vmulps        (%rax){1to16}, %zmm0, %zmm0
ret

и если b находится в xmm1, он производит

vbroadcastss    %xmm1, %zmm1
vmulps          %zmm1, %zmm0, %zmm0
ret

GCC уже будет выполнять случай vbroadcastss из регистра с встроенными функциями, но если b находится в памяти, компилирует его в vbroadcastss из памяти.

__m512 mul_broad(__m512 a, float b) {       
    __m512 bb = _mm512_set1_ps(b);
    __m512 ab = _mm512_mul_ps(a,bb);
    return ab;
}

clang будет использовать операнд широковещательной памяти, если b находится в памяти.


person Z boson    schedule 22.12.2015    source источник
comment
Я поставил вашу последнюю внутреннюю функцию на Godbolt. С -m32 (так что b находится в памяти) clang использует широковещательную загрузку. gcc использует vbroadcastss. (И вроде не работает, потому что делает бесполезный push ecx / lea ecx, ... / pop ecx) Может, пытается временно выровнять стек? В -O1 gcc использует ecx после lea.   -  person Peter Cordes    schedule 22.12.2015
comment
@PeterCordes, блин ... Clang снова побеждает! Не могу поверить, что не пробовал Clang. Как я могу сказать Clang / GCC, что b находится в памяти в 64-битном режиме?   -  person Z boson    schedule 22.12.2015
comment
Возможно, сделайте версию функции с float *pb arg.   -  person Peter Cordes    schedule 22.12.2015
comment
@PeterCordes, да, это работает. Думаю, я хотел смоделировать это с помощью static inline, но это показывает, чего я хочу.   -  person Z boson    schedule 22.12.2015
comment
Clang не нравится недопустимый синтаксис сборки% escape во встроенной строке сборки в vmulps (%%rdi)%{1to16%}, %%zmm0, %%zmm0\n\t".   -  person Z boson    schedule 22.12.2015
comment
Свертывание внутренней пары add / mul в FMA было бы совершенно неправильным, поэтому хорошо, что он этого не делает.   -  person harold    schedule 22.12.2015
comment
@harold было бы хорошо с -Ofast.   -  person Z boson    schedule 22.12.2015
comment
@harold: Удивительно, но gcc на самом деле делает это, даже без -ffast-math!. gcc всегда пытается воспользоваться любой аппаратной поддержкой FMA, о которой вы ему рассказываете. clang объединяет только функции add и mul вместе с -ffast-math. Я полагаю, что gcc не заботится о сохранении дополнительной точности, сверх того, что требует стандарт C. Я не читал FLT_EVAL_METHOD или что-то еще в последнее время.   -  person Peter Cordes    schedule 22.12.2015
comment
@PeterCordes, arggh ... GCC 4.9 этого не делает, а GCC 5.1 делает. Видимо исправили.   -  person Z boson    schedule 22.12.2015
comment
@PeterCordes, я думаю, нам просто нужно подождать, пока GCC исправит память вещания. AVX512 еще даже не вышел, а GCC не поддерживает встроенную функцию для KNC. Между тем, похоже, что Clang не поддерживает синтаксис {1to16} со встроенной сборкой, а GCC поддерживает.   -  person Z boson    schedule 22.12.2015
comment
@PeterCordes, обратите внимание, что использование FMA не теряет точности больше, чем без FMA. Во всяком случае, это лучше, так как это один режим округления, а не два режима округления. Я не уверен, какими должны быть правила для C. Я думаю, что для соответствия IEEE необходимо два режима округления. Поэтому было бы даже неправильно говорить, что нужна более свободная или расслабленная модель с плавающей запятой (например, -ffast-math). Просто для FMA нужна другая модель с плавающей запятой. Судя по всему, GCC даже не требует другой модели с плавающей запятой для FMA.   -  person Z boson    schedule 22.12.2015
comment
@Zboson: я имел в виду, что gcc не беспокоится об отказе от шага округления между mul и add, но теперь я понимаю, насколько то, что я сказал, было двусмысленным. Я думал, что строгие правила FP требуют от компилятора, по крайней мере, согласованности, но оказывается, что по умолчанию компиляторам явно разрешено заключать контракты, эффективно сохраняя бесконечную точность для временных файлов. Я поместил код на Godbolt для просмотра FLT_EVAL_METHOD с -m32 и эффекта -mfpmath=sse. (F_E_M = 2 или 0, с x87 / с SSE).   -  person Peter Cordes    schedule 23.12.2015
comment
Я думаю, что gcc поддерживает несколько альтернатив, поэтому вы можете дать ему несколько шаблонов с разными ограничениями, и он выберет код для версии, имеющей набор ограничений, с которым он может сравниться наиболее дешево. У меня возникли проблемы, пытаясь найти синтаксис для передачи переменной в регистре xmm и ссылки на тот же регистр с другой шириной. (например, %q[int_var], чтобы испустить %rax вместо %eax). Руководство GCC документирует префиксы только для целочисленных регистров.   -  person Peter Cordes    schedule 23.12.2015
comment
@PeterCordes, напишите ответ с кодом, пожалуйста. Вы намного лучше справляетесь со встроенной сборкой (и сборкой в ​​целом), чем я, поэтому я буду извлекать уроки из того, что вы публикуете.   -  person Z boson    schedule 23.12.2015
comment
Я собирался опубликовать один. Я все еще мог бы это сделать: P Я никогда не использовал альтернативные вещи, я только что видел их в руководстве. У меня возникли проблемы с версией широковещательной передачи из регистра, когда я пытался вызвать vpbroadcastss %[scalar], %%zmm_of_the_same_register. (Использование scalar в качестве операнда ввода / вывода. Хм, это ухудшит код для встроенной функции, если только параметр не поступает по неконстантной ссылке. О, на самом деле я мог бы просто солгать gcc и сказать ему, что я не напишите входной операнд, содержащий скаляр.Но меня беспокоит поломка при использовании на нижнем элементе нескалярного вектора.   -  person Peter Cordes    schedule 23.12.2015
comment
@PeterCordes, stackoverflow.com/ вопросы / 34436233 /   -  person Z boson    schedule 23.12.2015
comment
stackoverflow.com/questions/34459803/. Однако моя общая идея обречена: я наконец-то дошел до проверки документы о множественных альтернативных ограничениях Это не сработает: вы не можете указать другой шаблон для разных шаблонов ограничений. Я думал, что это сработает. Мне нужен if на чем-то вроде __builtin_constant_p(scalar), но проверять, нужно ли его загружать или нет.   -  person Peter Cordes    schedule 25.12.2015
comment
@PeterCordes, может быть, вы могли бы добавить свои источники для встроенной сборки GCC x86 в тег x86? Я считаю документацию сложной. Кажется, что есть несколько разбросанных фрагментов, которые объясняют несколько частей головоломки, но нет ни одного документа, который хорошо описывает встроенную сборку gcc. Что ты используешь?   -  person Z boson    schedule 25.12.2015
comment
@Zboson: Только сами документы. Ключ в том, чтобы понимать, что он предназначен для упаковки отдельных инструкций, которые компилятор не может использовать напрямую. Написание последовательностей или циклов, конечно, работает, но формулировка, подобная описанию Early-clobber, в котором говорится о записи инструкции ... перед чтением всех ее других операндов, говорит о варианте использования одной инструкции. Цель встроенного asm - описать asm компилятору, чтобы он мог вставить его в базовый блок, частью которого он является, и фактически оптимизировать его. Я понял это самостоятельно; Я нигде не читал.   -  person Peter Cordes    schedule 25.12.2015


Ответы (1)


Как отмечает Питер Кордес, GCC не позволяет вам указывать другой шаблон для разных альтернатив ограничений. Поэтому вместо этого в моем решении ассемблер выбирает правильную инструкцию в соответствии с выбранными операндами.

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

typedef __attribute__((vector_size(16))) float v4sf;

v4sf
foo(v4sf a, float b) {
    v4sf ret;
    asm(".ifndef isxmm\n\t"
        ".altmacro\n\t"
        ".macro ifxmm operand, rnum\n\t"
        ".ifc \"\\operand\",\"%%xmm\\rnum\"\n\t"
        ".set isxmm, 1\n\t"
        ".endif\n\t"
        ".endm\n\t"
        ".endif\n\t"
        ".set isxmm, 0\n\t"
        ".set regnum, 0\n\t"
        ".rept 8\n\t"
        "ifxmm <%2>, %%regnum\n\t"
        ".set regnum, regnum + 1\n\t"
        ".endr\n\t"
        ".if isxmm\n\t"
        "alt-1 %1, %2, %0\n\t"
        ".else\n\t"
        "alt-2 %1, %2, %0\n\t"
        ".endif\n\t"
        : "=x,x" (ret)
        : "x,x" (a), "x,m" (b));
    return ret;
}


v4sf
bar(v4sf a, v4sf b) {
    return foo(a, b[0]);
}

Этот пример должен быть скомпилирован с gcc -m32 -msse -O3 и должен генерировать два сообщения об ошибках ассемблера, подобные следующему:

t103.c: Assembler messages:
t103.c:24: Error: no such instruction: `alt-2 %xmm0,4(%esp),%xmm0'
t103.c:22: Error: no such instruction: `alt-1 %xmm0,%xmm1,%xmm0'

Основная идея здесь заключается в том, что ассемблер проверяет, является ли второй операнд (%2) регистром XMM или чем-то еще, предположительно ячейкой памяти. Поскольку ассемблер GNU не поддерживает много операций со строками, второй операнд сравнивается со всеми возможными регистрами XMM по одному в цикле .rept. Макрос isxmm используется для вставки %xmm и номера регистра вместе.

Для вашей конкретной проблемы вам, вероятно, придется переписать ее примерно так:

__m512
mul_broad(__m512 a, float b) {
    __m512 ret;
    __m512 dummy;
    asm(".ifndef isxmm\n\t"
        ".altmacro\n\t"
        ".macro ifxmm operand, rnum\n\t"
        ".ifc \"\\operand\",\"%%zmm\\rnum\"\n\t"
        ".set isxmm, 1\n\t"
        ".endif\n\t"
        ".endm\n\t"
        ".endif\n\t"
        ".set isxmm, 0\n\t"
        ".set regnum, 0\n\t"
        ".rept 32\n\t"
        "ifxmm <%[b]>, %%regnum\n\t"
        ".set regnum, regnum + 1\n\t"
        ".endr\n\t"
        ".if isxmm\n\t"
        "vbroadcastss %x[b], %[b]\n\t"
        "vmulps %[a], %[b], %[ret]\n\t"
        ".else\n\t"
        "vmulps %[b] %{1to16%}, %[a], %[ret]\n\t"
        "# dummy = %[dummy]\n\t"
        ".endif\n\t"
        : [ret] "=x,x" (ret), [dummy] "=xm,x" (dummy)
        : [a] "x,xm" (a), [b] "m,[dummy]" (b));
    return ret;
}
person Ross Ridge    schedule 25.12.2015
comment
Спасибо! Я попробую это сделать в ближайшие несколько дней, а затем вернусь к вам. - person Z boson; 28.12.2015