这个汇编语言是您想让编译器发出的。请不要实际使用内联汇编!)
Ryzen_encode_scalar:
lzcnt rcx, rdi
tzcnt rdx, rdi
mov eax, 63
sub eax, ecx
shl edx, 6
or eax, edx
ret
将低位索引转移平衡关键路径长度,从而实现更好的指令级并行性,如果高位需要63-CLZ
。
吞吐量:总共 7 条 uops,没有执行单元瓶颈。 因此,在每个时钟周期宽度为 5 uops 的情况下,这比每2个时钟周期更好。
Skylake_encode_scalar: ; input in RDI, output in EAX
tzcnt rax, rdi ; low bit. No false dependency on Skylake. GCC will probably xor-zero RAX because there is on Broadwell and earlier.
bsr rdi, rdi ; high bit index. same,same reg avoids false dep
shl eax, 6
or eax, edx
ret ; goes away with inlining.
这个操作从输入到输出有5个周期的延迟:在英特尔(Intel)上,bitscan指令需要3个周期,而在AMD上只需要1个。SHL + OR每个指令都会增加1个周期。
就吞吐量而言,我们只会在每个周期中出现一个位扫描(执行端口1)的瓶颈,因此我们可以每2个周期做一次编码,并且还剩下4个前端带宽的微操作可用于加载、存储和循环开销(或其他任务),假设我们有多个独立的编码要完成。
(但对于多个独立编码的情况,如果存在廉价的“vplzcntq”的仿真和数据来自内存,则SIMD对于AMD和英特尔(Intel)都可能更好。)
标量解码可以像这样:
decode:
xor eax, eax
bts rax, rdi
shr edi, 6
bts rax, rdi
ret
这段代码有三个位移操作(包括bts
),在Skylake处理器上只能使用端口0或端口6运行。因此,在Intel上,前端仅需4个微操作(每个时钟周期1个),但如果只运行此操作,每1.5个时钟周期只能解码1次,受到了位移吞吐量的瓶颈限制。
在4GHz的CPU上,每秒可解码26.66亿个指令,所以我们很好地达到了您的目标:)
而对于Ryzen处理器,bts reg,reg
是2个微操作,吞吐量为0.5c,但是shr
可以在任何端口上运行。 因此,它不会从bts
获取吞吐量,整个操作需要6个微操作(与Ryzen内核最窄处5单元相比)。所以每1.2个时钟周期可进行1次编码,只受到前端成本的限制。
如果有BMI2指令集,可以使用shlx rax, rbx, rdi
在寄存器中将一个带有1
的位替换掉xor清零+第一个BTS,并假定可以在循环中重复使用寄存器中的1
。
(这种优化完全依赖于编译器的发现;无标志位移只是更有效的复制和移位方法,可以在-march=haswell
或-march=znver1
或具有BMI2的其他目标上使用。)
无论如何,您只需编写retval = 1ULL << (packed & 63)
以解码第一个位。但如果您想知道哪些编译器会产生良好的代码,请查看以下内容。
std::unordered_map
甚至是std::map
总是可以的,但是要再度衡量。我真的不知道哪个哈希函数最适合这个键分布。甚至可能有一些位运算的技巧可以更快。 - Quimbytopbit = inputNumber & (inputNumber - 1); bottombit = inputNumber ^ topbit;
。 - melpomene