我想使用SSE内在函数翻译此代码。
for (uint32_t i = 0; i < length; i += 4, src += 4, dest += 4)
{
uint32_t value = *(uint32_t*)src;
*(uint32_t*)dest = ((value >> 16) & 0xFFFF) | (value << 16);
}
是否有人知道执行16位字交换的固有内容?
答案 0 :(得分:3)
pshufb
(SSSE3)应该比2班和OR更快。此外,稍微修改shuffle掩码将启用endian转换,而不仅仅是单词交换。
窃取Paul R的功能结构,只是替换了矢量内在函数:
void word_swapping_ssse3(uint32_t* dest, const uint32_t* src, size_t count)
{
size_t i;
__m128i shufmask = _mm_set_epi8(13,12, 15,14, 9,8, 11,10, 5,4, 7,6, 1,0, 3,2);
// _mm_set args go in big-endian order for some reason.
for (i = 0; i + 4 <= count; i += 4)
{
__m128i s = _mm_loadu_si128((__m128i*)&src[i]);
__m128i d = _mm_shuffle_epi8(s, shufmask);
_mm_storeu_si128((__m128i*)&dest[i], d);
}
for ( ; i < count; ++i) // handle residual elements
{
uint32_t w = src[i];
w = (w >> 16) | (w << 16);
dest[i] = w;
}
}
pshufb
可以有一个内存操作数,但它必须是shuffle掩码,而不是要被洗牌的数据。因此,您无法将其用作混乱负载。 :/
gcc没有为循环生成出色的代码。主循环是
# src: r8. dest: rcx. count: rax. shufmask: xmm1
.L16:
movq %r9, %rax
.L3: # first-iteration entry point
movdqu (%r8), %xmm0
leaq 4(%rax), %r9
addq $16, %r8
addq $16, %rcx
pshufb %xmm1, %xmm0
movups %xmm0, -16(%rcx)
cmpq %rdx, %r9
jbe .L16
由于所有这些循环开销,并且需要单独的加载和存储指令,吞吐量每2个周期仅为1次shuffle。 (8个uop,因为cmp
宏与jbe
融合。)
更快的循环是
shl $2, %rax # uint count -> byte count
# check for %rax less than 16 and skip the vector loop
# cmp / jsomething
add %rax, %r8 # set up pointers to the end of the array
add %rax, %rcx
neg %rax # and count upwards toward zero
.loop:
movdqu (%r8, %rax), %xmm0
pshufb %xmm1, %xmm0
movups %xmm0, (%rcx, %rax) # IDK why gcc chooses movups for stores. Shorter encoding?
add $16, %rax
jl .loop
# ...
# scalar cleanup
与矢量ALU操作不同, movdqu
加载可以与复杂的寻址模式进行微融合,所以除了商店之外,所有这些指令都是单uop。我知道。
这应该在每次迭代时以1个周期运行并进行一些展开,因为add
可以与jl
微融合。所以循环有5个uop。其中3个是加载/存储操作,具有专用端口。瓶颈是:pshufb
只能在一个执行端口上运行(Haswell(端口1和5上的SnB / IvB可以pshufb
))。每个周期一个商店(所有微观)。最后,对于Intel CPU,每个时钟限制的4个融合域uop,应该可以在Nehalem和更高版本(uop循环缓冲区)上禁止缓存丢失。
展开会使每16B的融合域uop总数低于4.增加指针,而不是使用复杂的寻址模式,会让商店微融合。 (减少循环开销总是很好:让重新排序缓冲区填满未来的迭代意味着CPU在循环结束时遇到错误预测并返回到其他代码时有事情要做。)
这正是你通过展开内在函数循环得到的,正如Elalfer正确地建议的那样是个好主意。使用gcc,如果代码没有过多膨胀,请尝试-funroll-loops
。
答案 1 :(得分:2)
你问题中的标量代码实际上不是字节交换(至少在字节序转换的意义上) - 它只是在32位字内交换高16位和低16位。如果这是您想要的,那么只需重新使用the solution to your previous question,并进行适当的更改:
public