Я пишу библиотеку кода на языке ассемблера x86-64, чтобы предоставить все обычные побитовые, сдвиговые, логические, сравнительные, арифметические и математические функции для s0128
, s0256
, s0512
, s1024
, s2048
и s4096
целочисленных типов со знаком и f0128
, f0256
, f0512
, f1024
, f2048
и f4096
типы с плавающей запятой.
Теперь я пишу некоторые процедуры преобразования типов и столкнулся с чем-то, что должно быть тривиально, но требует гораздо больше инструкций, чем я ожидал. Я чувствую, что должен что-то упустить (некоторые инструкции), чтобы сделать это проще, но пока не повезло.
Младшие 128 бит результата s0256
— это просто копия входного аргумента s0128
, а все биты старших 128 бит результата s0256
должны быть установлены на самый старший бит во входном аргументе s0128
.
Просто, да? Но вот пока лучшее, что я могу придумать, чтобы преобразовать s0256
в s0128
. Игнорируйте первые 4 строки (они просто проверяют ошибки аргументов) и последние 2 строки (возврат из функции без ошибок (rax == 0)). 5 строк в середине - это рассматриваемый алгоритм. Старайтесь избегать [условных] инструкций перехода.
.text
.align 64
big_m63:
.quad -63, -63 # two shift counts for vpshaq instruction
big_s0256_eq_s0128: # (s0256* arg0, const s0128* arg1); # s0256 = s0256(s0128)
orq %rdi, %rdi # is arg0 a valid address ???
jz error_argument_invalid # nope
orq %rsi, %rsi # is arg1 a valid address ???
jz error_argument_invalid # nope
vmovapd (%rsi), %xmm0 # ymm0 = arg1.ls64 : arg1.ms64 : 0 : 0
vmovhlps %xmm0, %xmm0, %xmm1 # ymm1 = arg1.ms64 : arg1.ms64 : 0 : 0
vpshaq big_m63, %xmm1, %xmm1 # ymm1 = arg1.sign : arg1.sign : 0 : 0
vperm2f128 $32, %ymm1, %ymm0, %ymm0 # ymm1 = arg1.ls64 : arg1.ms64 : sign : sign
vmovapd %ymm0, (%rdi) # arg0 = arg1 (sign-extended to 256-bits)
xorq %rax, %rax # rax = 0 == no error
ret # return from function
Эта процедура также неоптимальна в том смысле, что для каждой инструкции требуется результат предыдущей инструкции, что предотвращает параллельное выполнение любых инструкций.
Есть ли лучшая инструкция для сдвига вправо с расширением знака? Я не могу найти такую инструкцию, как vpshaq
, которая принимает немедленный байт для указания счетчика сдвига, хотя я не знаю почему (многие SIMD-инструкции имеют немедленные 8-битные операнды для различных целей). Кроме того, Intel не поддерживает vpshaq
. Ой!
Но смотри! У StephenCanon есть блестящее решение этой проблемы ниже! Потрясающий! В этом решении на одну инструкцию больше, чем в приведенном выше, но инструкция vpxor
может быть помещена после первой инструкции vmovapd
и фактически должна занимать не больше циклов, чем версия с 5 инструкциями выше. Браво!
Для полноты и удобства сравнения вот код с последним улучшением StephenCanon:
.text
.align 64
big_s0256_eq_s0128: # (s0256* arg0, const s0128* arg1); # s0256 = s0256(s0128)
orq %rdi, %rdi # is arg0 a valid address ???
jz error_argument_invalid # nope
orq %rsi, %rsi # is arg1 a valid address ???
jz error_argument_invalid # nope
vmovapd (%rsi), %xmm0 # ymm0 = arg1.ls64 : arg1.ms64 : 0 : 0
vpxor %xmm2, %xmm2, %xmm2 # ymm2 = 0 : 0 : 0 : 0
vmovhlps %xmm0, %xmm0, %xmm1 # ymm1 = arg1.ms64 : arg1.ms64 : 0 : 0
vpcmpgtq %xmm1, %xmm2, %xmm1 # ymm1 = arg1.sign : arg1.sign : 0 : 0
vperm2f128 $32, %ymm1, %ymm0, %ymm0 # ymm1 = arg1.ls64 : arg1.ms64 : sign : sign
vmovapd %ymm0, (%rdi) # arg0 = arg1 (sign-extended to 256-bits)
xorq %rax, %rax # rax = 0 == no error
ret # return from function
Я не уверен, но отсутствие необходимости чтения этих двух 64-битных счетчиков сдвига из памяти также может немного ускорить код. Хороший.
test %rdi, %rdi
/jz
для перехода к нулевому регистру. Это может объединяться в одну операцию тестирования и ответвления как на AMD, так и на Intel и позволяет избежать дополнительного цикла задержки в цепочке отложений, ведущей к нагрузке. Или, что еще лучше, потребуйте, чтобы ваш вызывающий объект передал допустимые аргументы, чтобы вы просто segfault использовали неверные указатели. - person Peter Cordes   schedule 21.10.2020