Faster floating point math with Rust’s new API
Faster floating point math with Rust’s new API
使用 Rust 新 API 加速浮点数运算
By Itamar Turner-Trauring | Last updated 02 Aug 2026 作者:Itamar Turner-Trauring | 最后更新:2026年8月2日
Floating point math is often slower than integer math because the compiler is being conservative about how it optimizes your code. While some programming languages already had solutions of a sort, until now Rust did not have a good stable way to deal with this limitation. But now, starting in version 1.98, Rust will allow telling the compiler it can optimize your code further—but with extra control so that you can still write numeric algorithms with minimal rounding errors. 浮点数运算通常比整数运算慢,因为编译器在优化代码时非常保守。虽然一些编程语言已经有了某种解决方案,但直到现在,Rust 还没有一种稳定且良好的方式来处理这一限制。不过,从 1.98 版本开始,Rust 将允许开发者告知编译器可以进一步优化代码,同时提供额外的控制权,以便你依然能够编写出舍入误差最小的数值算法。
In this article you will learn: 在这篇文章中,你将了解到:
- Why by default the compiler won’t optimize floating point math as much as it does integer math.
- 为什么编译器默认情况下不会像优化整数运算那样优化浮点数运算。
- Rust’s new API to solve this limitation.
- Rust 用于解决这一限制的新 API。
- Examples of using this new API, its speed impact, and how you can control where it is used.
- 使用该新 API 的示例、其对速度的影响,以及如何控制其应用范围。
Summing integers is fast
整数求和很快
I’m going to start with an example using integers, as a baseline of what sort of performance is possible. To get the fastest code generation, I’m telling Rust that it’s not 2004 anymore, and that it can generate CPU instructions that require modern hardware, namely x86-64 machines from the past 10 years or so. Specifically, all the code in this article is being compiled with RUSTFLAGS="-C target-cpu=x86-64-v3". (For maximum compatibility, in real-world usage you could provide a fallback implementation for older computers.)
我将从一个整数示例开始,作为衡量性能基准的参考。为了获得最快的代码生成速度,我告诉 Rust 现在已经不是 2004 年了,它可以生成需要现代硬件(即过去 10 年左右的 x86-64 机器)才能运行的 CPU 指令。具体来说,本文中的所有代码都是通过 RUSTFLAGS="-C target-cpu=x86-64-v3" 进行编译的。(为了实现最大兼容性,在实际应用中,你可以为旧计算机提供回退实现。)
Here’s a Rust function to sum a slice of int64 numbers:
这是一个对 int64 切片进行求和的 Rust 函数:
fn naive_sum_i64(values: &[i64]) -> i64 {
let mut total = 0;
for value in values {
total += value;
}
total
}
I’ll omit the code to expose this to Python, but it’s a variant of the Rust/Python code in a previous article. To benchmark it, I’ll create an array of integers in NumPy: 我将省略将其暴露给 Python 的代码,但这与我之前文章中的 Rust/Python 代码变体类似。为了进行基准测试,我将在 NumPy 中创建一个整数数组:
import numpy as np
DATA_INT = np.ones((1_000_000,), dtype=np.int64)
assert naive_sum_i64(DATA_INT) == 1_000_000
And now I can measure the speed of summing this array: 现在我可以测量对该数组求和的速度了:
| Code | Elapsed µ-seconds | CPU instructions per value |
|---|---|---|
naive_sum_i64(DATA_INT) | 168.1 | 0.5 |
| ➘ Lower numbers are better |
That’s 0.5 CPU instructions per value! How does that even work? Probably the compiler is using specialized Single Instruction, Multiple Data (SIMD) CPU instructions, that do batch operations on multiple values at once. The i7-12700K CPU I’m using here has 256-bit SIMD instructions, meaning it can do some specific operations on four 64-bit integers at a time. If there’s a specialized SIMD summing CPU instruction, the CPU would only need to loop 250,000 times and then sum 4 integers in each iteration. And in fact: 每个值仅需 0.5 条 CPU 指令!这是怎么做到的?编译器很可能使用了专门的单指令多数据流(SIMD)CPU 指令,可以一次性对多个值进行批量操作。我这里使用的 i7-12700K CPU 拥有 256 位 SIMD 指令,这意味着它一次可以对四个 64 位整数执行特定操作。如果存在专门的 SIMD 求和 CPU 指令,CPU 只需循环 250,000 次,每次迭代对 4 个整数求和即可。事实上:
| Code | Elapsed µ-seconds | CPU instructions | 256-bit SIMD integer instructions |
|---|---|---|---|
naive_sum_i64(DATA_INT) | 156.8 | 521,280 | 250,003 |
| ➘ Lower numbers are better |
In short, by using a specialized SIMD instruction, my CPU can sum integers very quickly. 简而言之,通过使用专门的 SIMD 指令,我的 CPU 可以非常快速地对整数求和。
Summing floats is slow?!
浮点数求和很慢?!
But what about floats—are they fast too? Again, I’ll create a million floating point values: 那么浮点数呢?它们也很快吗?同样,我将创建一百万个浮点数值:
# Array of 1M float64 values between 0 and 1.
DATA = np.random.random((1_000_000,))
I’ll implement a simple floating point sum function: 我将实现一个简单的浮点数求和函数:
fn naive_sum(values: &[f64]) -> f64 {
let mut total = 0.0;
for value in values {
total += value;
}
total
}
And compare the performance of summing integers and floats: 并比较整数和浮点数求和的性能:
| Code | Elapsed µ-seconds | CPU instructions | 256-bit SIMD int | 256-bit SIMD float |
|---|---|---|---|---|
naive_sum_i64(DATA_INT) | 151.9 | 521,214 | 250,003 | 0 |
naive_sum(DATA) | 595.2 | 1,458,269 | 0 | 0 |
| ➘ Lower numbers are better |
The floating point sum is much slower than the integer sum, and the compiler didn’t use SIMD float operations. Why the difference? 浮点数求和比整数求和慢得多,而且编译器没有使用 SIMD 浮点操作。为什么会有这种差异?
Floating point operations aren’t associative
浮点运算不满足结合律
Like most compilers, when Rust compiles your code in release mode it optimizes your code, transforming it in a variety of ways to (hopefully) make it faster. But there’s a promise compilers make when they do this: the optimized code will behave exactly the same as the unoptimized code. 像大多数编译器一样,当 Rust 在发布模式下编译代码时,它会优化代码,通过各种方式进行转换以(希望)使其运行得更快。但编译器在执行此操作时有一个承诺:优化后的代码行为必须与未优化的代码完全一致。
If I add three integers a, b, and c, a + (b + c) == (a + b) + c. That gives the compiler plenty of scope to optimize how the code runs, for example by using SIMD operations that might slightly change the order of additions.
如果我将三个整数 a、b 和 c 相加,a + (b + c) == (a + b) + c。这为编译器优化代码运行方式提供了很大的空间,例如通过使用可能会稍微改变加法顺序的 SIMD 操作。
Floating point numbers are different. For example, because floating point numbers span such a range of values, from tiny to huge, adding a sufficiently large number to a sufficiently small number results in that same large number: 浮点数则不同。例如,由于浮点数的取值范围非常广(从极小到极大),将一个足够大的数与一个足够小的数相加,结果仍是那个大数:
print( "Does adding a small number do nothing?", 1e16 + 1.0 == 1e16 )
# Does adding a small number do nothing? True
More broadly, for floating point numbers, a + (b + c) is not always the same as (a + b) + c, at least once you’re adding multiple numbers in a row. Let’s say I have an array that starts with 1e16 followed by many 1.0 values, and another that is the reverse. Summing these arrays will give different results:
更广泛地说,对于浮点数而言,a + (b + c) 并不总是等于 (a + b) + c,至少在连续相加多个数字时是这样。假设我有一个数组,开头是 1e16,后面跟着许多 1.0,另一个数组则相反。对这些数组求和会得到不同的结果:
import math
HIGH_VALUE_FIRST = np.ones((1_000_000,), dtype=np.float64)
HIGH_VALUE_FIRST[0] = 1e16
HIGH_VALUE_LAST = np.ones((1_000_000,), dtype=np.float64)
HIGH_VALUE_LAST[-1] = 1e16
print( "Is the sum the same?", naive_sum(HIGH_VALUE_FIRST) == naive_sum(HIGH_VALUE_LAST) )
# Is the sum the same? False
Because the order of summing impacts the result, the compiler assumes, as it should, that I asked for this particular order for a reason. As a result, the compiler will not reorder these operations. Nor will it apply any other optimization that might change the results, even if the resulting code is slower. 由于求和顺序会影响结果,编译器理所当然地认为我要求这种特定的顺序是有原因的。因此,编译器不会重新排序这些操作。它也不会应用任何可能改变结果的其他优化,即使这会导致代码运行变慢。
Rust’s new algebraic operators: telling the compiler when to be flexible
Rust 的新代数运算符:告知编译器何时可以灵活处理
While conservatism on the part of the compiler is the right default, sometimes you as the programmer know that re-ordering operations isn’t a problem. In that situation, it would be good to be able to tell the compiler that while over here it should not change the order of operations, over there it’s actually fine. Starting in Rust 1.98 there is a new feature that allows just that. In addition to the normal arithmetic operations you can do on floating point numbers, there are new set of so-called “algebraic” arithmetic operators. 虽然编译器的保守是正确的默认设置,但有时作为程序员,你知道重新排序操作并不会产生问题。在这种情况下,如果能告诉编译器“此处不应改变运算顺序,但彼处可以灵活处理”将会非常有帮助。从 Rust 1.98 开始,一项新功能正好实现了这一点。除了可以对浮点数执行的常规算术运算外,现在还有一组所谓的“代数”算术运算符。