如何在 IA32 上将带符号的整数求和为更宽的和。 32 位有符号整数的 64 位总和?
How to sum integers with sign into a wider sum on IA32. 64-bit sum of 32-bit signed ints?
我正在尝试对 Assembly 32 上带符号的整数列表求和,但我只需要对不带符号的整数求和。你知道一些方法吗?
我的程序尝试对整数求和并存储在 resultado
中,其大小为 64 位,因此为了能够做到这一点,我使用了两个 32 位寄存器(EAX 和 EDX),并检查何时总和产生进位。
之后,我在 resultado 上加入了 EAX 和 EDX。
# sum.s Sumar los elementos de una lista.
# llamando a función, pasando argumentos mediante registros
# retorna: código retorno 0, comprobar suma en %eax mediante gdb/ddd.
# as --32 -g sum.s -o sum.o
# ld -m elf_i386 sum.o -o sum
# DATA SECTION
.section .data
lista:
.int 4294967295, 4294967295, 4294967295, 4294967295
longlista:
.int (.-lista)/4
resultado:
.quad -1
.section .text
_start: .global _start
mov $lista, %ebx
mov longlista, %ecx
call suma
mov %eax, resultado
mov %edx, resultado+4
mov , %eax
mov [=10=], %ebx
int [=10=]x80
suma:
push %esi
mov [=10=], %eax
mov [=10=], %edx
mov [=10=], %esi
bucle:
add (%ebx,%esi,4), %eax
jc .L1
bucle1:
inc %esi
cmp %esi,%ecx
jne bucle
pop %esi
ret
.L1:
inc %edx
jmp bucle1
这给出了一个 64 位和,将输入视为无符号 32 位,这不是我想要的。
由于仅使用 32 位寄存器,下一个使用 64 位加法的代码将为正数和负数给出正确的总和,而无需任何回绕。
签名结果可以超出范围[-2GB,+2GB-1].
suma:
push %esi
push %edi
xor %esi, %esi ;Clear %edi:%esi
xor %edi, %edi
sub , %ecx ;Start at last element in array
jl emptyArray
bucle:
mov (%ebx,%ecx,4), %eax ;From signed 32-bit to signed 64-bit
cdq
add %eax, %esi ;Add signed 64-bit numbers
adc %edx, %edi
dec %ecx
jge bucle
emptyArray:
mov %esi, %eax ;Move result from %edi:%esi to %edx:%eax
mov %edi, %edx
pop %edi
pop %esi
ret
添加的顺序并不重要,因此代码从最后一个元素开始,向第一个元素靠拢。
您当前的代码隐式零扩展。它等同于 add (%ebx,%esi,4), %eax
/ adc [=13=], %edx
,但是您需要添加到上半部分的是 0 或 -1
,具体取决于下半部分的符号。 (即符号位的 32 个副本;参见 Sep 的回答)。
32 位 x86 可以直接使用 SSE2/AVX2/AVX512 paddq
进行 64 位整数数学运算。 (所有支持 64 位的 CPU 都支持 SSE2,因此现在这是一个合理的基准)。
(或者 MMX paddq
如果您关心 Pentium-MMX 到 Pentium III / AMD Athlon-XP)。
SSE4.1 使符号扩展到 64 位变得便宜。
pmovsxdq (%ebx), %xmm1 # load 2x 32-bit (Dword) elements, sign-extending into Qword elements
paddq %xmm1, %xmm0
add , %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 [=10=]b11101110, %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)
我避免了索引寻址模式so the load can stay micro-fused with pmovsxdq
on Sandybridge。索引在 Nehalem、Haswell 或更高版本或 AMD 上很好。
不幸的是,有 没有 SSE4.1 的 CPU 仍在使用中。在这种情况下,您可能只想使用标量,但您可以手动进行符号扩展。
虽然没有 64 位算术右移。 (仅 64 位元素大小逻辑移位)。但是您可以通过复制并使用 32 位移位广播符号位来模拟 cdq
,然后解压缩。
# 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 , %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 , %esi
jnc .loop # loop upward toward zero, with %ebx pointing to the end of the array.
#end of one loop iteration, does 16 bytes
(使用两个单独的向量累加器可能比使用两个 paddq
到 xmm0
更好,以保持依赖链更短。)
这是更多指令,但每次迭代执行的元素数量是原来的两倍。每个 paddq
仍然有更多指令,但它可能仍然比标量更好,尤其是在 Broadwell 之前的英特尔 CPU 上,其中 adc
是 2 微指令(因为它有 3 个输入:2 个寄存器 + EFLAGS)。
在第一个 psrad
之前复制 %xmm1 两次可能会更好。在 movdqa
具有非零延迟的 CPU 上,我想复制然后使用原始文件来缩短关键路径,这样乱序执行就可以减少隐藏的延迟。
但这意味着最后一个 punpck
正在读取 2x movdqa
寄存器副本链的结果。这在 上可能更糟。它可能需要向量 ALU 来进行复制,因为 mov
寄存器复制链是移动消除无法完美工作的情况之一。
我正在尝试对 Assembly 32 上带符号的整数列表求和,但我只需要对不带符号的整数求和。你知道一些方法吗?
我的程序尝试对整数求和并存储在 resultado
中,其大小为 64 位,因此为了能够做到这一点,我使用了两个 32 位寄存器(EAX 和 EDX),并检查何时总和产生进位。
之后,我在 resultado 上加入了 EAX 和 EDX。
# sum.s Sumar los elementos de una lista.
# llamando a función, pasando argumentos mediante registros
# retorna: código retorno 0, comprobar suma en %eax mediante gdb/ddd.
# as --32 -g sum.s -o sum.o
# ld -m elf_i386 sum.o -o sum
# DATA SECTION
.section .data
lista:
.int 4294967295, 4294967295, 4294967295, 4294967295
longlista:
.int (.-lista)/4
resultado:
.quad -1
.section .text
_start: .global _start
mov $lista, %ebx
mov longlista, %ecx
call suma
mov %eax, resultado
mov %edx, resultado+4
mov , %eax
mov [=10=], %ebx
int [=10=]x80
suma:
push %esi
mov [=10=], %eax
mov [=10=], %edx
mov [=10=], %esi
bucle:
add (%ebx,%esi,4), %eax
jc .L1
bucle1:
inc %esi
cmp %esi,%ecx
jne bucle
pop %esi
ret
.L1:
inc %edx
jmp bucle1
这给出了一个 64 位和,将输入视为无符号 32 位,这不是我想要的。
由于仅使用 32 位寄存器,下一个使用 64 位加法的代码将为正数和负数给出正确的总和,而无需任何回绕。
签名结果可以超出范围[-2GB,+2GB-1].
suma:
push %esi
push %edi
xor %esi, %esi ;Clear %edi:%esi
xor %edi, %edi
sub , %ecx ;Start at last element in array
jl emptyArray
bucle:
mov (%ebx,%ecx,4), %eax ;From signed 32-bit to signed 64-bit
cdq
add %eax, %esi ;Add signed 64-bit numbers
adc %edx, %edi
dec %ecx
jge bucle
emptyArray:
mov %esi, %eax ;Move result from %edi:%esi to %edx:%eax
mov %edi, %edx
pop %edi
pop %esi
ret
添加的顺序并不重要,因此代码从最后一个元素开始,向第一个元素靠拢。
您当前的代码隐式零扩展。它等同于 add (%ebx,%esi,4), %eax
/ adc [=13=], %edx
,但是您需要添加到上半部分的是 0 或 -1
,具体取决于下半部分的符号。 (即符号位的 32 个副本;参见 Sep 的回答)。
32 位 x86 可以直接使用 SSE2/AVX2/AVX512 paddq
进行 64 位整数数学运算。 (所有支持 64 位的 CPU 都支持 SSE2,因此现在这是一个合理的基准)。
(或者 MMX paddq
如果您关心 Pentium-MMX 到 Pentium III / AMD Athlon-XP)。
SSE4.1 使符号扩展到 64 位变得便宜。
pmovsxdq (%ebx), %xmm1 # load 2x 32-bit (Dword) elements, sign-extending into Qword elements
paddq %xmm1, %xmm0
add , %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 [=10=]b11101110, %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)
我避免了索引寻址模式so the load can stay micro-fused with pmovsxdq
on Sandybridge。索引在 Nehalem、Haswell 或更高版本或 AMD 上很好。
不幸的是,有 没有 SSE4.1 的 CPU 仍在使用中。在这种情况下,您可能只想使用标量,但您可以手动进行符号扩展。
虽然没有 64 位算术右移。 (仅 64 位元素大小逻辑移位)。但是您可以通过复制并使用 32 位移位广播符号位来模拟 cdq
,然后解压缩。
# 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 , %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 , %esi
jnc .loop # loop upward toward zero, with %ebx pointing to the end of the array.
#end of one loop iteration, does 16 bytes
(使用两个单独的向量累加器可能比使用两个 paddq
到 xmm0
更好,以保持依赖链更短。)
这是更多指令,但每次迭代执行的元素数量是原来的两倍。每个 paddq
仍然有更多指令,但它可能仍然比标量更好,尤其是在 Broadwell 之前的英特尔 CPU 上,其中 adc
是 2 微指令(因为它有 3 个输入:2 个寄存器 + EFLAGS)。
在第一个 psrad
之前复制 %xmm1 两次可能会更好。在 movdqa
具有非零延迟的 CPU 上,我想复制然后使用原始文件来缩短关键路径,这样乱序执行就可以减少隐藏的延迟。
但这意味着最后一个 punpck
正在读取 2x movdqa
寄存器副本链的结果。这在 mov
寄存器复制链是移动消除无法完美工作的情况之一。