gpt4 book ai didi

c - SSE 字节和半字交换

转载 作者:太空宇宙 更新时间:2023-11-04 05:54:17 25 4
gpt4 key购买 nike

我想使用 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 位字交换的内在函数吗?

最佳答案

pshufb (SSSE3) 应该比 2 个类次和一个 OR 更快。此外,对 shuffle 掩码的轻微修改将启用字节序转换,而不仅仅是单词交换。

窃取了 Paul R 的函数结构,只是替换了 vector 内在函数:

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 可以有一个内存操作数,但它必须是洗牌掩码,而不是要洗牌的数据。所以你不能将它用作随机加载。 :/

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 次洗牌。 (8 微指令,因为 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

movdqu 加载可以与复杂的寻址模式微融合,这与 vector ALU 操作不同,所以我相信除了存储之外所有这些指令都是单 uop。

由于 add 可以与 jl 微融合,因此每次迭代应该运行 1 个周期并进行一些展开。所以这个循环总共有 5 个微指令。其中 3 个是加载/存储操作,它们具有专用端口。瓶颈是:pshufb 只能在一个执行端口上运行(Haswell(SnB/IvB 可以 pshufb 在端口 1 和 5 上运行))。每个周期一个商店(所有微架构)。最后,英特尔 CPU 的每个时钟 4 个融合域微指令限制,这应该是可以达到的,除非在 Nehalem 和更高版本(微指令循环缓冲区)上出现缓存未命中。

展开将使每 16B 的总融合域 uops 低于 4。递增指针,而不是使用复杂的寻址模式,将使存储微融合。 (减少循环开销总是好的:让重新排序缓冲区充满 future 的迭代意味着当 CPU 在循环结束时遇到错误预测并返回到其他代码时,CPU 有事情要做。)

正如 Elalfer 正确建议的那样,这几乎就是展开内在循环所获得的结果。使用 gcc,尝试 -funroll-loops 如果这不会使代码膨胀太多。

顺便说一句,在加载或存储时与其他代码混合进行字节交换可能比将缓冲区转换为单独的操作更好。

关于c - SSE 字节和半字交换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31203907/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com