在左移一位的特殊情况下,您可以使用paddb xmm0, xmm0.
正如 Jester 在评论中指出的那样,模拟不存在的情况的最佳选择psrlb是psllb使用更宽的移位,然后屏蔽掉任何跨越元素边界的位。
例如
psrlw xmm0, 2 ; doesn't matter what size (w/d/q): performance is the same for all sizes on all CPUs
pand xmm0, [mask_right2]
section .rodata
align 16
;; required mask depends on the shift count
mask_right2: times 16 db 0xff >> 2 (16 bytes of 0x3f)
Run Code Online (Sandbox Code Playgroud)
或者以其他方式在循环之前将 0x3f 广播到向量寄存器中,例如vpbroadcastd内存vbroadcastss中的 dword 中的 或 、movddupqword 中的 SSE3 或只是movdqa向量加载。(vpbroadcastb需要额外的 ALU uop,与双字或更广泛的广播不同,它们只是简单的加载)。或者使用像//这样的序列动态生成 。通过正确选择移位计数,您可以生成 2 n -1 字节的任何模式(重复的零,然后重复的)。pcmpeqd xmm0,xmm0psrlw xmm0, 8+2packuswb xmm0,xmm0
mov r32, imm32/movd xmm, r32和 shuffle 也是一个选项,但与 / ... 序列相比可能不会节省指令字节pcmpeqw。(请注意,寄存器源版本VBROADCASTSS仅适用于 AVX2,这在这里并不重要,因为 256b 整数移位也是仅适用于 AVX2。)
对于可变计数向量移位,在整数寄存器中创建掩码并将其广播到向量是一种选择(pshufb与全零寄存器一起使用来广播低字节,或者使用+imul eax, eax, 0x01010101从字节到双字))。您还可以使用该方法创建全一向量并使用 a和 then或。movdpshufdpcmpeqdpsrlw xmm0, xmm1packpshufb
我没有看到任何类似的有效方法来模拟算术右移(不存在PSRAB)。每个字的高字节由 正确处理PSRAW。将每个字的低字节移至高位将使另一个字根据PSRAW需要多次复制其符号位。
;; vpblendvb is 2 uops on Intel so this is worse throughput in loops than the pxor/paddb version
;; Latency may be the same on Skylake because this has some ILP.
; input in xmm0. Using AVX to save on mov instructions
VPSLLDQ xmm1, xmm0, 1 ; or VPSLLW xmm1, xmm0, 8, but this distributes one of the uops to the shuffle port
VPSRAW xmm1, xmm1, 8+2 ; shift low bytes back to final destination
VPSRAW xmm0, xmm0, 2 ; shift high bytes, leaving garbage in low bytes
VPBLENDVB xmm0, xmm1, xmm0, xmm2 ; (where xmm2 holds a mask of alternating 0 and -1, which could be generated with pcmpeqw / psrlw 8). This insn is fairly slow
Run Code Online (Sandbox Code Playgroud)
不存在与字节粒度的立即混合,因为单个立即字节只能编码 8 个元素。
没有 VPBLENDVB (如果生成或加载常量很慢,即使它可用,也可能更好):
;; Probably worse than the PXOR/PADDB version, if 2 constants are cheap to load
;; Needs no vector constants, but this is inefficient vs. versions with constants.
VPSLLDQ xmm1, xmm0, 1 ; or VPSLLW 8
VPSRAW xmm1, xmm1, n ; low bytes in the wrong place
VPSRAW xmm0, xmm0, 8+n ; shift high bytes all the way to the bottom of the element
VPSLLW xmm0, xmm0, 8 ; high bytes back in place, with zero in the low byte. (VPSLLDQ can't work: PSRAW 8+n leaves garbage we need to clear)
VPSRLW xmm1, xmm1, 8 ; shift low bytes into place, leaving zero in the high byte. (VPSRLDQ 1 could do this, if we started with VPSLLW instead of VPSLLDQ)
VPOR xmm0, xmm0, xmm1
Run Code Online (Sandbox Code Playgroud)
在寄存器中使用带有常量(交替 0/-1 字节)的 PAND/PANDN/POR 也可以进行字节混合(对移位端口的压力要小得多),并且如果您必须这样做,这是一个更好的选择这是一个循环。
假设每个字节都是零扩展的,例如在使用 AND + 移位/AND 将半字节解包为字节之后。(适用于任何字段宽度,只需调整常数即可。)
使用 XOR 翻转高位零和符号位。将 1 添加到符号位,这样它将恢复正确的符号位,并通过进位传播清除高位(如果它变为 0 并执行)或保留它们设置(如果它变为 1 并且不进位)。
; hoist the constants out of a loop if you're looping, of course.
; input in XMM0, upper bits of each byte already zeroed
pxor xmm0, [const_0xf8] ; 1111 s'xxx
paddb xmm0, [const_0x08] ; 0000 0xxx or 1111 1xxx
Run Code Online (Sandbox Code Playgroud)
psrab只需内存中的 2 个常量,这仍然是可能的。这很可能是循环的最佳选择,特别是如果您有空闲的寄存器来提升这些常量的负载。(如果您也需要的话0xf0,可以与 一起使用vpandn来隔离低半字节。)
psrld xmm0, 4 ; ???? sxxx (s = sign bit, xxx = lower bits)
por xmm0, xmm5 ; set1_epi8(0xf0) ; 1111 sxxx
pxor xmm0, xmm6 ; set1_epi8(0x08) ; 1111 s'xxx
paddb xmm0, xmm6 ; set1_epi8(0x08) ; 0000 0xxx or 1111 1xxx
Run Code Online (Sandbox Code Playgroud)
我认为我们无法避免使用两个单独的布尔值。我们需要 PXOR 来计数器 PADDB 或 PSUBB 翻转符号位,但只有 POR 可以设置位,无论其旧值如何。
我们可以在加或减之前隔离符号位并左移它(pand + pslld + paddb),但这会更糟,特别是没有 AVX 用于 3 操作数指令以避免 movdqa。这也将是更全面的指令,包括我们仍然需要的 POR。
优点:
vpblendvb。缺点:
vpblendvb版本更好,尤其是在 AMD Zen / Zen2 上,其中vpblendvb单微指令指令只有 1c 延迟。代替 pxor / paddb,使用pshufb基于低 4 位查找每个字节的新值。不幸的是,pshufb如果选择器设置了高位,则将通道清零,因此我们无法在psrld可能已移入非零高位的原始结果上使用它。
const __m128i sext_lut = _mm_setr_epi8( 0, 1, 2, 3, 4, 5, 6, 7,
-8, -7, -6, -5, -4, -3, -2, -1);
return _mm_shuffle_epi8(sext_lut, v);
Run Code Online (Sandbox Code Playgroud)
对于 3 操作数非破坏性的 AVX,这可以是重复使用寄存器中的查找表的单个指令。如果没有,则需要movdqa复制 LUT。
以此进行转移:
__m128i srai_4_epi8(__m128i v) {
v = _mm_srli_epi32(v, 4);
v = _mm_and_si128(v, _mm_set1_epi8(0x0f));
const __m128i sext_lut = _mm_setr_epi8( 0, 1, 2, 3, 4, 5, 6, 7,
-8, -7, -6, -5, -4, -3, -2, -1);
return _mm_shuffle_epi8(sext_lut, v);
}
Run Code Online (Sandbox Code Playgroud)