2016-11-16 14 views
7

Что лучше всего загружать и хранить сгенерированные целевые регистры в/из регистров SIMD? До сих пор я использовал стек как временный. Например,Лучший способ загрузить/сохранить из/в регистры общего назначения в/из регистра xmm/ymm

mov [rsp + 0x00], r8 
mov [rsp + 0x08], r9 
mov [rsp + 0x10], r10 
mov [rsp + 0x18], r11 
vmovdqa ymm0, [rsp] ; stack is properly aligned first. 

Я не думаю, что есть какая-либо команда может сделать это непосредственно (или в другом направлении), так как это означало бы инструкцию с пятью операндами. Однако код выше кажется мне глупым. Есть ли лучший способ сделать это? Я могу думать только об одной альтернативе, используйте pinsrd и соответствующие инструкции. Но это ничуть не лучше.

Мотивация заключается в том, что когда-то в AVX2 есть что-то еще, а другие с регистром общего назначения. Например, скажем, в небольшом фрагменте кода есть четыре 64-битных целых числа без знака, мне понадобятся четыре xor, два mulx от BMI2. Это будет быстрее сделать xor с vpxor, однако mulx не имеет эквивалента AVX2. Любая производительность усиления vpxor против 4 xor теряется из-за процесса упаковки и распаковки.

ответ

5

Является ли ваша нехватка узких мест, пропускная способность или объединенных доменов? Если это латентность, то сохранение/перезагрузка ужасно, потому что магазин-переадресация ларька из узких магазинов в широкую нагрузку.

Для пропускной способности и совместимости с доменами, это не ужасно: всего 5 подключенных доменов uops, узкое место на порту магазина. Если окружающий код в основном ALU uops, его стоит рассмотреть.


Для потребительной случае вы предлагаете:

потратив много инструкций/микрооперации на перемещение данных между целыми и векторными регуляров, как правило, плохая идея. PMULUDQ дает вам эквивалент 32-битной мульхи, но вы правы, что 64-битные умножения недоступны непосредственно в AVX2. (У них есть AVX512).

Вы можете использовать 64-разрядный векторный умножитель, используя обычные методы расширенной точности с PMULUDQ. Мой ответ на Fastest way to multiply an array of int64_t? обнаружил, что векторизация 64 x 64 => 64b множителей стоила этого с векторами AVX2 256b, но не с векторами 128b. Но это было с данными в памяти, а не с данными, начинающимися и заканчивающимися векторными правилами.

В этом случае может стоит строить 64x64 => 12 полного умножения из множественных 32х32 => 64-разрядных векторный размножается, но это может занять так много инструкций, что это просто не стоит. Если вам нужны результаты в верхней половине, то лучше всего распаковать скаляр (или сделать все скалярное сканирование).

Целочисленный XOR чрезвычайно дешев, с отличным ILP (латентность = 1, пропускная способность = 4 за часы). Это определенно не стоит переносить ваши данные в векторные regs только на XOR, если у вас нет ничего, что могло бы быть удобным для вектора. Смотрите ссылки tag wiki.


Вероятно, лучший способ для латентности является:

vmovq xmm0, r8 
vmovq xmm1, r10   # 1uop for p5 (SKL), 1c latency 
vpinsrq xmm0, r9, 1   # 2uops for p5 (SKL), 3c latency 
vpinsrq xmm1, r11, 1 
vinserti128 ymm0, ymm0, ymm1, 1 # 1uop for p5 (SKL), 3c latency 

Итого: 7 микрооперации для p5, с достаточно НРП, чтобы запустить их почти все обратно к спине. Поскольку предположительно r8 будет готов к циклу или двум раньше, чем r10, в любом случае, вы не потеряете много.


Также стоит задуматься: что бы вы ни делали, чтобы произвести r8..r11, сделать это с вектором целочисленных инструкций, так что ваши данные уже в XMM регуляров. Затем вам все равно нужно перетасовать их вместе, с 2x PUNPCKLQDQ и VINSERTI128.

+0

Спасибо за подробный ответ еще раз. «xor», вероятно, плохой пример. Дело в том, что все, кроме 'mulx', можно сделать с помощью AVX2. Тем не менее, этого недостаточно, чтобы оправдать стоимость загрузки/хранения. Кроме того, загрузка с YMM на r/64 потребует нескольких перетасовки/перестановки или с помощью 'pextrq' и т. Д. Хотя некоторые задержки могут быть скрыты за счет того, что несколько блоков (YMM) обрабатываются в одной итерации цикла. Я думаю, мне просто нужно поэкспериментировать и узнать для себя. –

+0

@ YanZhou: YMM-> integer с store/reload значительно ниже латентности, чем в другом направлении, потому что функция store-forwarding работает из выровненного широкого магазина, чтобы узкие нагрузки, которые полностью перекрывают его. Кроме того, нагрузки имеют в два раза большую пропускную способность магазинов. Возможно, что извлечение скаляра для чего-то может стоить того, если будет достаточно векторной работы. –

+0

@YanZhou: О, я просто вспомнил, что создание 64-битного вектора, умножающегося на 32-битное векторное умножение, может быть фактически более эффективным, чем переход к скаляру и обратно. См. [Мой ответ на этот вопрос] (http://stackoverflow.com/questions/37296289/fastest-way-to-multiply-an-array-of-int64-t) для эффективного 64 x 64 => 64-битного векторного умножения , Если вам нужны результаты в верхней половине для 64 x 64 => 128 бит векторного умножения, потребуются дополнительные инструкции. (Я забыл, насколько больше работы, может быть, слишком много.) –