Assembly h表示i+j=k加上最终进位
ck=∑i、 j=0..n;i+j=kai·bj·2i+j+Ck 术语Ck是进位,当它向高位传播时,它只取决于低位。Assembly h表示i+j=k加上最终进位,assembly,x86,x86-64,multiplication,unsigned,Assembly,X86,X86 64,Multiplication,Unsigned,ck=∑i、 j=0..n;i+j=kai·bj·2i+j+Ck 术语Ck是进位,当它向高位传播时,它只取决于低位。 第二项不能有i或j>k的ai或bj,就好像第一项为真,然后i=k+e,对于正的、非空的e,因此j=k-i=k-k-e=-e 但是j不能是负的 第二种情况类似,留给读者 附录B 正如BeeOnRope在评论中指出的,如果只需要部分结果,处理器可能不会计算完整结果 你可能是说这只是一种概念上的思考方式。当您使用64x64->64格式时,处理器不必执行完整的128位乘法。事实上,在最近
第二项不能有i或j>k的ai或bj,就好像第一项为真,然后i=k+e,对于正的、非空的e,因此j=k-i=k-k-e=-e
但是j不能是负的
第二种情况类似,留给读者 附录B 正如BeeOnRope在评论中指出的,如果只需要部分结果,处理器可能不会计算完整结果 你可能是说这只是一种概念上的思考方式。当您使用64x64->64格式时,处理器不必执行完整的128位乘法。事实上,在最近的Intel上,截断的形式只需要1个uop,但完整的形式需要2个uop,因此需要做一些额外的工作 此外,符号扩展可能在概念上也是如此
类似地,符号扩展可能“在概念上”发生,但可能不会在硬件中发生。他们不会有额外的电线和晶体管来做符号或零扩展,这会给已经很大的乘法器增加很多体积,但是会使用一些其他的技巧来做乘法,就好像发生了那样
†长度为n的二进制数的数量级为2n,因此两个此类数的乘法数量级为2n·2n=2n+n=22 n。就像长度为2 n的数字一样。我假设这是针对x86的?是的,我可以选择更好的标记:)两个操作数
imul
只返回乘积的下半部分,因此符号性不受关注。哪个位是符号位?最重要的位,其权重为-2^(k-1)而不是+2^(k-1)根据哈罗德的说法,fun2也是32位乘法而不是64位乘法?但是fun2
也使用imul
,但采用无符号整数。我的意思是它是64x64->64,而不是64x64->128。签名只影响128位productGreat答案的上半部分!我想你可能想澄清一下,当你说这样的话时,不管使用什么形式,处理器总是以两倍于操作数的大小计算结果(即,像第一种形式),你可能的意思是这只是一种概念上的思考方式。当您使用64x64->64格式时,处理器不必执行完整的128位乘法。事实上,在最近的Intel上,截断形式只需要1个uop,但完整形式需要2个uop,因此需要做一些额外的工作。类似地,符号扩展可能“在概念上”发生,但可能不会在硬件中发生。他们不会有额外的电线和晶体管来做符号或零扩展,这会给已经很大的乘法器增加很多体积,但会使用其他一些技巧来做乘法,就好像发生了那样。@BeeOnRope:我认为imul r64
中的额外uop只是写高半部;UOP在Intel uarches中最多可以有一个非标志输出。而imul r32
必须对乘法器输出进行拆分和/或零扩展,使其不会自然拆分(在64位边界处),因此为3 UOP。更新了Margaret的答案,添加了一个TL:DR和一些uop。@PeterCordes是的,我想你肯定需要一个额外的uop来写入高半部,但我不清楚是否调用了整个64->128电路,而忽略了高输出。宽乘法肯定比窄乘法更昂贵,但不清楚是否值得仅仅为了节省一点功率而抑制结果的高部分(或者在乘法器的设计中这是否可行)@BeeOnRope:如果为了节省电能,晶体管的位翻转只会导致高半衰期,我一点也不会感到惊讶,特别是对于vpmullw
vs.vpmulhw
和integer。可能不是电源门控高半部,因为它都是连接的。对于窄输入,128不翻转的高64可能会自然发生,例如对于mul r32
,但是imul r32、r32
或imul r64、r64
的进一步选通是可能的。但这只是电源方面的考虑,而不是时钟对时钟/uop性能,这正是我在编辑中试图指出的。
#include <stdint.h>
uint64_t usquare(uint32_t x) {
return (uint64_t)x * (uint64_t)x;
}
0: 89 f8 mov eax,edi
2: 48 0f af c0 imul rax,rax
6: c3 ret
0: 48 0f af ff imul rdi,rdi
4: 48 89 f8 mov rax,rdi
7: c3 ret
#include <stdint.h>
uint64_t fun0 ( uint32_t x )
{
return (uint64_t)x * (uint64_t)x;
}
uint64_t fun1 ( uint32_t x )
{
return ((uint64_t)x) * ((uint64_t)x);
}
uint64_t fun2 ( uint64_t x )
{
return (x * x);
}
0000000000000000 <fun0>:
0: 89 f8 mov %edi,%eax
2: 48 0f af c0 imul %rax,%rax
6: c3 retq
7: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
e: 00 00
0000000000000010 <fun1>:
10: 89 f8 mov %edi,%eax
12: 48 0f af c0 imul %rax,%rax
16: c3 retq
17: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
1e: 00 00
0000000000000020 <fun2>:
20: 48 89 f8 mov %rdi,%rax
23: 48 0f af c7 imul %rdi,%rax
27: c3 retq
0x00FF * 0x00FF = 0xFE01
0xFFFF * 0xFFFF = 0xFFFE0001
so
0xFF * 0xFF = 0x01
imul ax ;16x16->32, Result is dx:ax
imul rax ;64x64->128, Result is rdx:rax
imul ax, ax ;16x16->16, Lower WORD of the result is ax
imul rax, rax ;64x64->64, Lower QWORD of the result is rax
How mul extends How imul extends
and operand and operand
+----+ +----+ +----+ +----+
|0...| |1...| |0...| |1...|
+----+ +----+ +----+ +----+
+----+----+ +----+----+ +----+----+ +----+----+
|0000|0...| |0000|1...| |0000|0...| |1111|1...|
+----+----+ +----+----+ +----+----+ +----+----+
#include <stdint.h>
uint64_t foo(uint32_t a)
{
return a*(uint64_t)a;
}
foo(unsigned int):
mov eax, edi ;edi = a
imul rax, rax ;64x64->64
ret
foo(unsigned int):
mov eax, DWORD PTR [esp+4]
mul eax
ret