22

我们是否仍然需要在软件中模拟 128 位整数,或者这些天在您的普通桌面处理器中是否有硬件支持?

4

5 回答 5

18

x86-64 指令集可以使用一条指令执行 64 位 * 64 位到 128 位(mul对于每个有一个操作数的无符号符号imul),所以我认为在某种程度上 x86 指令集确实包含一些支持128 位整数。

如果您的指令集没有执行 64-bit*64-bit 到 128-bit 的指令,那么您需要几条指令来模拟这个

这就是为什么 128 位 * 128 位到更低的 128 位操作可以用 x86-64 的少量指令完成的原因。例如与 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 位加法。

于 2015-12-12T12:37:59.283 回答
13

我将通过将桌面处理器与简单的微控制器进行比较来解释它,因为算术逻辑单元 (ALU) 是 CPU 中的计算器,以及Microsoft x64 调用约定System-V 调用约定的相似操作. 对于简短的答案滚动到最后,但长答案是通过比较 x86/x64 与 ARM 和 AVR 最容易看出区别:

长答案

本机双字整数乘法架构支持比较

中央处理器 单词 x 单词 => dword 双字 x 双字 => 双字
M0 否(仅 32x32 => 32)
AVR 8x8 => 16(仅限某些版本)
M3/M4/A 是(32x32 => 64)
x86/x64 是(高达 64x64 => 128) 是(对于 x64,最高 64x64 => 64)
SSE/SSE2/AVX/AVX2 是(32x32 => 64 SIMD 元素) 否(最多 32x32 => 32 个 SIMD 元素)

如果您了解此图表,请跳至简答

智能手机、PC 和服务器中的 CPU 具有多个 ALU,它们对各种宽度的寄存器执行计算。另一方面,微控制器通常只有一个 ALU。CPU 的字长与 ALU 的字长不同,尽管它们可能相同,Cortex-M0 就是一个很好的例子。

ARM 架构

Cortex-M0 是一个 Thumb-2 处理器,它是一个紧凑的(主要是 16 位)指令编码,用于 32 位架构。(寄存器和 ALU 宽度)。Cortex-M3/M4 有更多指令,包括smull/ umull、32x32 => 64 位加宽乘法,有助于扩展精度。尽管存在这些差异,但所有 ARM CPU 共享相同的架构寄存器集,这很容易从 M0 升级到 M3/M4 和更快的 Cortex-A 系列智能手机处理器以及NEON SIMD 扩展

ARM 架构寄存器

ARM 架构

在执行二元运算时,值溢出寄存器是很常见的(即太大而无法放入寄存器)。ALU 具有 n 位输入和 n 位输出,带有进位(即溢出)标志。

在此处输入图像描述

加法不能在一条指令中执行,但需要相对较少的指令。但是,对于乘法,您需要将字长加倍以适应结果,并且当您需要 2n 个输出时,ALU 只有 n 个输入和 n 个输出,这样就行不通了。例如,通过将两个 32 位整数相乘,您需要一个 64 位的结果,而两个 64 位整数需要最多 128 位的结果以及 4 个字大小的寄存器;2 还不错,但是 4 变得复杂,而且你的寄存器用完了。CPU 处理此问题的方式将有所不同。对于 Cortex-M0,没有相关指令,但对于 Cortex-M3/M4,有一条指令用于 32x32=>64 位寄存器乘法,需要 3 个时钟周期。

(您可以使用 Cortex-M0 的 32x32 => 32 位muls作为 16x16=>32 位构建块来进行更大的乘法运算;这显然效率低下,但可能仍然比手动移位和有条件相加要好。)

AVR 架构

AVR 微控制器有 131 条指令,可在 32 个 8 位寄存器上运行,按寄存器宽度分类为 8 位处理器,但它同时具有 8 位和 16 位 ALU。如果没有软件破解,AVR 处理器无法使用两个 16 位寄存器对或 64 位整数数学进行 16x16=>32 位计算。这与寄存器组织和 ALU 溢出操作中的 x86/x64 设计相反。这就是 AVR 被归类为 8/16 位 CPU 的原因。你为什么在乎?它会影响性能和中断行为。

AVR“小”,而其他没有“增强”指令集的设备根本没有硬件乘法。但如果完全支持,mul指令是 8x8 => 16 位硬件乘法。https://godbolt.org/z/7bbqKn7Go展示了 GCC 如何使用它。

AVR 架构寄存器

在此处输入图像描述

x86 架构

在 x86 上,可以使用 MUL 指令将两个 32 位整数相乘以创建一个 64 位整数,从而在 EDX:EAX 中生成无符号 64 位,或在 RDX:RAX 对中生成 128 位结果。

在 x86 上添加 64 位整数只需要两条指令(add/adc由于进位标志),对于 x86-64 上的 128 位也是如此。但是将两个寄存器整数相乘需要更多的工作。

例如,在 32 位 x86 上,64x64 => 64 位乘法 ( long long) 需要大量指令,包括 3 次乘法(低 x 低扩展,叉积不需要,因为我们不需要完整结果)。这是 x86 的 32x64=>64 位 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 软件在 64 位或更宽的整数数学运算中比 x86 软件运行得更快的主要原因:您可以在一条指令中完成这项工作!您可以想象 x86 模式下的 128 位乘法运算会非常昂贵,它是. x64 与 x86 非常相似,只是位数是 x64 的两倍

x86 架构寄存器

x86 架构寄存器

x64 架构寄存器

x64 架构寄存器

当 CPU 将 2 个字大小的寄存器配对以创建单个双字大小的值时,在堆栈上,生成的双字值将与 RAM 中的字边界对齐。除了两个寄存器对之外,四字数学是一种软件破解。这意味着对于 x64,可以组合两个 64 位寄存器来创建一个 128 位寄存器对溢出,该溢出与 RAM 中的 64 位字边界对齐,但是 128x128=>128 位数学是软件破解。

然而,x86/x64 是超标量 CPU,而您所知道的寄存器仅仅是架构寄存器。在幕后,有更多的寄存器可以帮助优化 CPU 流水线以使用多个 ALU 执行乱序指令。

SSE/SSE2 引入了 128 位SIMD寄存器,但没有指令将它们视为单个宽整数。有paddq两个并行的 64 位加法,但不支持 128 位加法的硬件,甚至不支持手动传播跨元素的进位。最宽的乘法是两个 32x32=>64 并行操作,是 x86-64 scalar 可以做的宽度的一半mul。请参阅长整数例程可以从 SSE 中受益吗?对于最先进的技术,以及您必须跳过的圈子才能从 SSE/AVX 中获得非常大的整数的任何好处。

即使使用 AVX-512(用于 512 位寄存器),最宽的 add / mul 指令仍然是 64 位元素。x86-64 确实在 SIMD 元素中引入了 64x64 => 64 位乘法。

简短的回答

C++ 应用程序处理 128 位整数的方式将根据操作系统或裸机调用约定而有所不同。微软有他们自己的约定,令我沮丧的是,生成的 128 位返回值不能作为单个值从函数返回。Microsoft x64 调用约定规定,当返回一个值时,您可以返回一个 64 位整数或两个 32 位整数。例如,您可以这样做word * word = dword,但在 Visual-C++ 中,您必须使用_umul128来返回 HighProduct,而不管它是否在 RDX:RAX 对中。我哭了,很难过。:-(

然而, System-V调用约定确实允许在 RAX:RDX 中返回 128 位返回类型。https://godbolt.org/z/vdd8rK38e。(并且 GCC / clang 必须__int128让编译器发出必要的指令来 2 寄存器 add/sub/mul 和 div 的辅助函数 - gcc 中有 128 位整数吗?

至于您是否应该依赖 128 位整数支持,很少遇到使用 32 位 x86 CPU 的用户,因为它们太慢了,因此设计软件在 32 位 x86 CPU 上运行并不是最佳实践因为它增加了开发成本并可能导致用户体验下降;期望 Athlon 64 或 Core 2 Duo 达到最低规格。您可以预期代码在 Microsoft 上的性能不如 Unix 操作系统。

英特尔架构寄存器是一成不变的,但英特尔和 AMD 不断推出新的架构扩展,但编译器和应用程序需要很长时间才能更新,你不能指望它跨平台。您需要阅读Intel 64 and IA-32 Architecture Software Developer's ManualAMD64 Programmers Manual

于 2018-06-09T17:18:32.487 回答
4

简短的回答是:不!

更详细地说,SSE 寄存器是 128 位宽的,但不存在将它们视为 128 位整数的指令。充其量,这些寄存器被视为两个 64 位(无)符号整数。可以通过并行添加这两个 64 位值并手动处理溢出来构造诸如加法/...之类的操作,但不能使用单个指令。实现它可能会变得非常复杂和“丑陋”,请看这里:

如何将两个 SSE 寄存器加在一起

与使用 64 位通用寄存器的实现(软件中的“仿真”)相比,这对于每个基本操作都必须完成,其优势可能值得怀疑。另一方面,这种 SSE 方法的一个优点是,一旦实现,它也适用于 256 位整数 (AVX2) 和 512 位整数 (AVX-512),只需稍作修改。

于 2015-12-12T09:18:30.500 回答
3

5年后;这个问题的答案仍然是“否”。

具体来说,让我们将其分解为 80x86 的各种操作:

整数加法

不支持 128 位。一直支持“大于本机支持的”整数运算(例如addthen adc)。几年前,英特尔 ADX(多精度加进指令扩展)的引入改进了对“比本机支持的更大”整数运算的支持,以便可以在保留其他标志的同时完成它(这在循环中可能很重要 -例如,其他标志控制退出条件的地方)。

整数减法

不支持 128 位。一直支持“大于本机支持的”整数运算(例如subthen sbb)。这没有改变(英特尔的 ADX 扩展不包括减法)。

整数乘法

不支持 128 位(将 128 位整数与 128 位整数相乘)。一直支持“大于本机支持的”整数运算(例如,将 64 位整数相乘并得到 128 位结果)。

整数除法

不支持 128 位(将 128 位整数除以 128 位整数)。一直部分支持“大于本机支持的”整数运算(例如,将 128 位整数除以 64 位整数并获得 64 位结果),当除数为 128 位时,这无济于事。

整数移位

不支持 128 位。一直支持“大于本机支持的”整数运算(例如shldand shrd, and rcrand rcl)。

原子

大多数情况下不支持 128 位。有一条lock cmpxchg16b指令(在引入长模式后不久引入)可用于在单个指令中模拟 128 位“原子加载”或在单个指令中模拟 128 位“如果相等则原子比较”(“ atomic store”和“atomic exchange”需要一个重试循环)。注意:对齐的 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 位,但很难热情(由于“有用性与开销”妥协)。

于 2021-05-13T02:26:59.557 回答
2

RISC-V 有一个 128b 的候选 ISA,RV128I。

https://github.com/brucehoult/riscv-meta/blob/master/doc/src/rv128.md

然而,此时它只是一个架构,并没有被冻结。

于 2021-05-13T02:45:59.363 回答