您当前的代码隐式零扩展。它相当于add (%ebx,%esi,4), %eax/ adc $0, %edx,但您需要添加到上半部分的是 0 或-1取决于下半部分的符号。(即符号位的 32 个副本;参见 Sep 的答案)。
32 位 x86 可以直接使用 SSE2/AVX2/AVX512 进行 64 位整数数学运算paddq。(所有支持 64 位的 CPU 都支持 SSE2,所以现在这是一个合理的基准)。
(或 MMX paddq,如果您通过 Pentium III / AMD Athlon-XP 关心 Pentium-MMX)。
SSE4.1 使符号扩展到 64 位变得便宜。
pmovsxdq (%ebx), %xmm1 # load 2x 32-bit (Dword) elements, sign-extending into Qword elements
paddq %xmm1, %xmm0
add $8, %ebx
cmp / jb # loop while %ebx is below an end-pointer.
# preferably unroll by 2 so there's less loop overhead,
# and so it can run at 2 vectors per clock on SnB and Ryzen. (Multiple shuffle units and load ports)
# horizontal sum
pshufd $0b11101110, %xmm0, %xmm1 # xmm1 = [ hi | hi ]
paddq %xmm1, %xmm0 # xmm0 = [ lo + hi | hi + hi=garbage ]
# extract to integer registers or do a 64-bit store to memory.
movq %xmm0, (result)
我避免了索引寻址模式,因此负载可以与Sandybridge保持微融合pmovsxdq。索引在 Nehalem、Haswell 或更高版本或 AMD 上都很好。
不幸的是,没有 SSE4.1 的 CPU仍在使用中。在这种情况下,您可能只想使用标量,但您可以手动进行符号扩展。
但是,没有 64 位算术右移。(仅 64 位元素大小的逻辑移位)。但是您可以cdq通过复制并使用 32 位移位来广播符号位,然后解压缩来进行模拟。
# prefer running this on aligned memory
# Most CPUs without SSE4.1 have slow movdqu
.loop:
movdqa (%ebx, %esi, 1), %xmm1 # 4x 32-bit elements
movdqa %xmm1, %xmm2
psrad $31, %xmm1 # xmm1 = high halves (broadcast sign bit to all bits with an arithmetic shift)
movdqa %xmm2, %xmm3 # copy low halves again before destroying.
punpckldq %xmm1, %xmm2 # interleave low 2 elements -> sign-extended 64-bit
paddq %xmm2, %xmm0
punpckhdq %xmm1, %xmm3 # interleave hi 2 elements -> sign-extended 64-bit
paddq %xmm3, %xmm0
add $16, %esi
jnc .loop # loop upward toward zero, with %ebx pointing to the end of the array.
#end of one loop iteration, does 16 bytes
(使用两个单独的向量累加器可能比使用两个paddqinto更好xmm0,以保持依赖链更短。)
这是更多指令,但每次迭代执行的元素数量是原来的两倍。每个指令仍然更多paddq,但它可能仍然比标量更好,尤其是在 Broadwell 之前的英特尔 CPU 上,其中adc2 微指令(因为它有 3 个输入:2 个寄存器 + EFLAGS)。
在第一次之前复制 %xmm1 两次可能会更好psrad。在movdqa具有非零延迟的 CPU 上,我想复制然后使用原始文件来缩短关键路径,以便乱序执行具有更少的延迟隐藏。
但这意味着最后一个punpck是读取 2xmovdqa寄存器副本链的结果。对于 mov-elimination 不能 100% 工作的 CPU (Intel),这可能会更糟。它可能需要一个向量 ALU 来进行复制,因为mov寄存器副本链是 mov 消除不能完美工作的情况之一。