我们是否还需要在软件中模拟128位整数,或者现在普通桌面处理器是否支持硬件?
答案 0 :(得分:11)
x86-64指令集可以使用一条指令执行64位* 64位到128位(mul
表示无符号imul
,每个指令用一个操作数进行签名)所以我认为在某种程度上,x86指令集确实包含对128位整数的一些支持。
如果您的指令集没有执行64位* 64位到128位的指令,那么您需要several instructions to emulate this。
这就是使用x86-64的少量指令可以完成128位* 128位到低位128位操作的原因。例如,使用GCC
__int128 mul(__int128 a, __int128 b) {
return a*b;
}
生成此程序集
imulq %rdx, %rsi
movq %rdi, %rax
imulq %rdi, %rcx
mulq %rdx
addq %rsi, %rcx
addq %rcx, %rdx
使用一个64位* 64位到128位指令,两个64位* 64位降低64位指令,以及两个64位加法。
答案 1 :(得分:6)
我将通过将台式机处理器与简单微控制器进行比较来解释它,因为算术逻辑单元(ALU)(CPU中的计算器)和Microsoft x64 Calling Convention vs的操作类似System-V Calling Convention。对于简短的答案滚动到最后,但长期的答案是通过比较x86 / x64与ARM和AVR最容易看出差异:
原生双字整数数学架构支持比较
| CPU | word x word => dword | dword x dword => dword |
|:-----------------:|:--------------------:|:----------------------:|
| M0 | No | No |
| AVR | No | No |
| M3/M4/A | Yes | No |
| x86/x64 | Yes | No |
| SSE/SSE2/AVX/AVX2 | Yes | Yes |
智能手机,PC和服务器中的CPU具有多个ALU,可对各种宽度的寄存器执行计算。另一方面,微控制器通常只有一个ALU。 CPU的字大小与ALU的字大小不同,尽管它们可能相同,但Cortex-M0是一个主要的例子。
Cortex-M0是一个Thumb-2 a Von Neuman Architecture Processor,这意味着它主要是一个16位的Thumb16 CPU,但它有一个32位的ALU。在汇编中,您将拥有大多数16位指令,当您有32位指令时,您将该字加载到32位寄存器并使用两个16位指令。这与功能齐全的32位Harvard Architecture处理器Cortex-M3 / M4形成鲜明对比。尽管存在这些差异,但所有ARM CPU都共享相同的集合或架构寄存器,这些寄存器很容易从M0升级到M3 / M4以及更快的Cortex-A系列智能手机处理器NEON SIMD extentions。
当执行二进制操作时,值通常会溢出寄存器(即变得太大而无法放入寄存器)。 ALU具有n位输入和n位输出,带有进位(即溢出)标志。
不能在一条指令中执行添加,但需要相对较少的指令。但是,用于乘法,您需要将字长加倍以适应结果,当您需要2n输出时,ALU只有n个输入和n个输出,因此无法工作。例如,通过将两个32位整数相乘,您需要64位结果,而两个64位整数需要最多128位结果,其中包含4个字大小的寄存器; 2也不错,但是4变得复杂并且你的注册用完了。 CPU处理这种情况的方式会有所不同。对于Cortex-M0,没有相关说明,因为它是Thumb-2,但是对于Cortex-M3 / M4,有一条指令用于32x32 => 64位寄存器乘法,需要3个时钟周期。
AVR微控制器有131条指令,可在32个8位寄存器上工作,按指令计数分为8位处理器,但它同时具有8位和16位ALU。 The AVR processor cannot do 16x16=>32-bit calculations有两个16位寄存器对或64位整数数学而没有软件黑客攻击。这与寄存器组织和ALU溢出操作中的x86 / x64设计相反。这就是AVR被归类为8/16位CPU的原因。你为什么在乎?它会影响性能和中断行为。
在x86上,可以使用MUL指令将两个32位整数相乘以创建64位整数,从而在EDX:EAX中产生无符号64位,或者在RDX:RAX对中产生128位结果。在x86上乘以两个64位整数或在x64上乘以两个128位整数然而不是同一个故事。在x86上添加64位整数需要很少的指令,因为从寄存器到寄存器的进位标志仅处理LSB或MSB,但64位乘法需要 A LOT 指令。 Here is an example of 32x64=>64-bit x86 signed multiply assembly for x86:
movl 16(%ebp), %esi ; get y_l
movl 12(%ebp), %eax ; get x_l
movl %eax, %edx
sarl $31, %edx ; get x_h, (x >>a 31), higher 32 bits of sign-extension of x
movl 20(%ebp), %ecx ; get y_h
imull %eax, %ecx ; compute s: x_l*y_h
movl %edx, %ebx
imull %esi, %ebx ; compute t: x_h*y_l
addl %ebx, %ecx ; compute s + t
mull %esi ; compute u: x_l*y_l
leal (%ecx,%edx), %edx ; u_h += (s + t), result is u
movl 8(%ebp), %ecx
movl %eax, (%ecx)
movl %edx, 4(%ecx)
x86支持配对两个寄存器以存储完整的乘法结果(包括高半部分),但是您不能使用这两个寄存器来执行64位ALU的任务。这是x64软件比x86软件运行速度更快的主要原因:您可以在一条指令中完成工作!你可以想象x86模式下的128位乘法计算成本非常高,it is。 The x64 is very similar to x86 except with twice the number of bits
当CPU将2个字大小的寄存器配对以创建单个双字大小的值时,在堆栈中,生成的双字值将与RAM中的字边界对齐。除了两个寄存器对之外,四字数学是一个软件黑客。这意味着对于x64,可以组合两个64位寄存器以创建128位寄存器对溢出,该溢出与RAM中的64位字边界对齐,但128x128 => 128位数学是软件黑客。 / p>
然而,x86 / x64是superscalar CPU,你知道的寄存器只是architectural registers。在幕后,有更多寄存器可帮助优化CPU流水线,以使用多个ALU执行乱序指令。虽然x64可能不是128位CPU,但SSE / SSE2引入了原生128位数学,AVX引入了256位本机整数数学,而AVX2引入了512位整数数学。从函数返回时,将返回128位XMM0 SSE / SSE2寄存器中的值,256位AVX结果为YMM0,512位AVX2结果为ZMM0;但是,这些是x86 / x64的附加组件,而不是主要的体系结构,并且支持完全依赖于编译器和发布平台(如Python)。
C ++应用程序处理128位整数的方式因操作系统或调用约定的裸机而异。微软有他们自己的约定,令我非常沮丧的是,得到的128位返回值 CAN NOT 作为单个值从函数返回。 Microsoft x64 Calling Convention指示在返回值时,您可以返回一个64位整数或两个32位整数。例如,您可以执行word * word = dword
,但在Visual C ++中,您必须使用_umul128返回HighProduct,无论它是否在RDX:RAX对中。我哭了,很伤心。但是,System-V调用约定允许在RAX中返回128位返回类型:RDX。但CPU架构寄存器 DO NOT 完全支持128位整数数学,这是以{SSE / SSE2开头的SIMD向量处理扩展。
至于你是否应该依靠128位整数支持,使用32位x86 CPU的用户非常罕见因为它们太慢所以设计软件不是最好的做法在32位x86 CPU上运行,因为它会增加开发成本并可能导致用户体验下降;期望Athlon 64或Core 2 Duo达到最低规格。您可以预期代码在Microsoft上的运行效果不如Unix OS。
英特尔架构寄存器一脉相承,但英特尔和AMD不断推出新的架构扩展,但编译器和应用程序需要很长时间才能进行更新,您不能指望跨平台。您需要阅读Intel 64 and IA-32 Architecture Software Developer’s Manual和AMD64 Programmers Manual。
答案 2 :(得分:2)
简短回答是:不!
详细说明,SSE寄存器是128位宽,但没有指令将它们视为128位整数。最好将这些寄存器视为两个64位(un)有符号整数。可以通过并行添加这两个64位值并手动处理溢出来构建添加/ ...等操作,但不能使用单个指令。实现这一点可能会变得非常复杂,而且很难看到#34;,请看这里:
How can I add together two SSE registers
与使用64位通用寄存器("仿真"在软件中)的实现相比,这必须针对每个基本操作进行,并且可能具有可疑的优点。另一方面,这种SSE方法的一个优点是,一旦实现,它也可以用于256位整数(AVX2)和512位整数(AVX-512),只需要很小的修改。 / p>
答案 3 :(得分:2)
5年后;这个问题的答案仍然是“否”。
具体来说,让我们将其分解为 80x86 的各种操作:
整数加法
不支持 128 位。一直支持“大于原生支持”的整数运算(例如 add
然后 adc
)。几年前,英特尔 ADX(多精度加进位指令扩展)的引入改进了对“比本机支持的更大”整数运算的支持,这样就可以在保留其他标志的同时完成它(这在循环中可能很重要 -例如,其他标志控制退出条件的地方)。
整数减法
不支持 128 位。一直支持“大于原生支持”的整数运算(例如 sub
然后 sbb
)。这没有改变(英特尔的 ADX 扩展不包括减法)。
整数乘法
不支持 128 位(128 位整数乘以 128 位整数)。一直支持“大于原生支持”的整数运算(例如,将 64 位整数相乘并获得 128 位结果)。
整数除法
不支持 128 位(128 位整数除以 128 位整数)。一直有部分支持“大于原生支持”的整数运算(例如,将 128 位整数除以 64 位整数并获得 64 位结果),这在除数为 128 位时无济于事。>
整数移位
不支持 128 位。一直支持“比原生支持更大”的整数运算(例如 shld
和 shrd
,以及 rcr
和 rcl
)。
原子
大多不支持 128 位。有一条 lock cmpxchg16b
指令(在引入 long 模式后不久引入)可用于在单个指令中模拟 128 位“原子加载”或在单个指令中模拟 128 位“原子比较,如果相等”单个指令(“原子存储”和“原子交换”需要重试循环)。 注意:不保证对齐的 SSE 和 AVX 加载/存储是原子的(实际上,对于所有读取和写入大小相同的简单情况,它们可能是也可能不是“伪原子”)。
按位运算(AND、OR、XOR)
对于通用寄存器,没有。对于SIMD,自2000年引入SSE2以来就支持128位(之前支持长模式和64位通用寄存器);但这在极少数情况下很有用(例如,您不执行 128 位操作的混合操作,并且可以避免将值移入/移出 SSE 寄存器的成本)。
位域/位串操作(设置、清除和测试位域中的单个位)
“部分支持”。如果位域在 register/s 中,则不支持 128 位位域。如果位域在内存中,那么 80x86 已经支持大得多的大小(16 位代码中最多 65536 位位域,32 位代码中最多 4294967296 位位域等)。这包括对位域(lock bts ..
等)的原子操作。
地址
不支持(对于物理地址或虚拟地址)。我们甚至都没有完整的 64 位地址。有一个“5 级分页”扩展可以将虚拟地址从 48 位增加到 57 位,但很难热情(由于“有用性与开销”的妥协)。
答案 4 :(得分:1)
RISC-V 有一个 128b 的候选 ISA,RV128I。
https://github.com/brucehoult/riscv-meta/blob/master/doc/src/rv128.md
然而,在这一点上,它只是一个架构,并没有被冻结。