引言:小世界中的大数字
想象一下:您正在编写一个加密算法、物理过程模拟,或者只是计算到最近星系的距离(以纳米为单位)。突然您意识到——标准的i64(从-9百亿亿到9百亿亿)完全不够用。Rust中有一个救生圈——i128类型。但它究竟如何在物理上无法处理128位数字的处理器上工作?这难道是魔法吗?
剧透:没有魔法,只有天才的工程学。在本文中,我们将剖析Rust(更准确地说,是底层的LLVM)如何在64位架构上模拟i128。您将了解到为什么它并不像看起来那么慢,以及如何使用这些巨型数字编写高效代码。这不是枯燥的理论——我们将通过示例,从问题出发,一路走到实际应用。
1. 硬件现实:为什么128位是一个挑战
现代64位处理器(x86-64、ARM64)拥有64位的通用寄存器。这是它们的“原生”字长。要处理128位数字,处理器必须对两个64位半部分(低位和高位)执行多次操作。
在Rust中,i128类型(及其无符号兄弟u128)不是硬件类型,而是一个聚合类型。它在内存中存储为两个相邻的64位字。编译器(通过LLVM)生成将这些字作为一个整体处理的代码。
一个简单的例子:
fn main() { let big: i128 = 123456789012345678901234567890; println!("{}", big);}在汇编层面,这将转化为一系列操作两个64位寄存器(例如rax和rdx)的指令。LLVM非常擅长高效地“拼接”这些操作。
2. 加法剖析:Rust如何相加i128
让我们看看最基本的操作——两个i128相加。编写一个简单的函数,并使用cargo-show-asm或Compiler Explorer一探究竟。
fn add_big(a: i128, b: i128) -> i128 { a + b}为x86-64编译(不优化,以便更清晰)。LLVM将生成大致如下的伪代码:
- 将
a的低64位加载到寄存器rax,高位加载到rdx。 - 将
b的低64位加载到rbx,高位加载到rcx。 - 相加低位部分:
add rax, rbx——结果在rax中,如果发生溢出则设置进位标志(CF)。 - 考虑进位相加高位部分:
adc rdx, rcx——如果CF标志被设置,adc(带进位加法)会加1。 - 结果:低64位在
rax,高64位在rdx。
这就是模拟。两条指令代替一条。乘法和除法的代码会更复杂,但原理相同——分解为64位操作。
3. 性能如何?迷思与现实
许多人认为i128比i64慢两倍。这不完全正确。是的,每个操作会变成2-3条指令而不是一条。但现代处理器具有:
- 流水线和超标量架构——每个时钟周期可以执行多条指令。
- 分支预测——对于比较操作尤其重要。
- 大缓存——内存操作不受影响,因为
i128只占用16字节。
实际测试(Rust,循环中的简单算术):
use std::time::Instant;
fn main() { let n = 100_000_000; // i64 let start = Instant::now(); let m