当前位置:首页>java>纯 Rust 实现 SIMD 编程:让你的代码飞起来

纯 Rust 实现 SIMD 编程:让你的代码飞起来

  • 2026-09-18 09:41:16
纯 Rust 实现 SIMD 编程:让你的代码飞起来

引言

2026 年初,AMD Zen 5 CPU 的发布为开发者带来了令人兴奋的消息——这是首款拥有完整 512 位数据通路的 AMD 处理器。这意味着我们终于可以放心使用 AVX-512 SIMD 指令,而不必担心降频和其他性能问题。

对于 Rust 开发者来说,这无疑是一份意外的惊喜。Rust 让我们能够轻松地为热点代码添加 SIMD 加速,而无需编写汇编代码。在实际测试中,纯 Rust 实现的 ChaCha12 在 AWS m8a.2xlarge 实例上可以达到 6.7 GB/s 的吞吐量,ChaCha20 达到 5.1 GB/s,BLAKE3 更是高达 10.8 GB/s。

本文将带你深入了解如何在纯 Rust 中编写 SIMD 加速代码,无需 nightly 版本即可上手。

什么是 SIMD?

SIMD 是 Single Instruction, Multiple Data(单指令多数据)的缩写,指的是能够同时操作更大数据向量的 CPU 指令。

普通的 CPU 指令通常处理最多 64 位的数据,我们称之为"标量指令"。而 SIMD 指令则允许 CPU 处理更大的数据,对于 x86_64 架构的 AVX-512 指令集来说,最高可达 512 位。我们称这些为"向量指令"。

下面是一个伪代码示例,展示如何将 4 个 uint64 数值各加 10:

// 传统方式:逐个处理
let
 mut a = [1, 2, 3, 4];
for
 n in &a {
    *n += 10;
}

// SIMD 方式:并行处理

let
 mut vector = u64x4::from_array([1, 2, 3, 4]); // 创建一个 256 位向量,包含 4 个 uint64
let
 x = u64x4::splat(10); // 创建一个 256 位向量:(10, 10, 10, 10)
let
 vector = vector + x;
// 结果:vector = u64x4(11, 12, 13, 14)

传统方式需要循环执行多次,而向量化代码大约只需要 3 条指令即可完成。

SIMD 编程思维

使用 SIMD 指令可以概括为三个步骤:加载 → 计算 → 存储。

第一步:加载数据

将数据从内存加载到向量寄存器中:

// 将值为 1 的 int64 加载 8 次到一个 512 位向量中
let
 v1 = _mm512_set1_epi64(1);

// 将包含 8 个元素的(非对齐)int64 数组加载到 512 位向量中

let
 v2 = _mm512_loadu_epi64([1, 2, 3, 4, 5, 6, 7, 8]);

第二步:执行计算

执行加法、异或、减法等运算:

// 并行执行 8 个 64 位通道的加法运算
let
 v_result = _mm512_add_epi64(v1, v2);
// v_result = __m512i(2, 3, 4, 5, 6, 7, 8, 9)

第三步:存储结果

将结果写回内存:

let result = [0i64; 8];
_mm512_storeu_epi64(result.as_mut_ptr(), v_result);
// result = [2, 3, 4, 5, 6, 7, 8, 9]

需要注意的是,从内存加载和存储数据的延迟成本相对较高,因此应尽量减少这类操作。让数据"保温"在 SIMD 寄存器中是更好的选择。

了解你的目标平台

实现 SIMD 加速代码需要投入时间,也会增加维护负担,因此你需要了解代码将在哪里运行:

如果代码主要运行在高端 Intel/AMD 处理器(如服务器)上,专注于 AVX-512 的优化可能就足够了。

如果代码主要运行在消费级设备上,那么专注于 AVX2 和 NEON(ARM64)可能是更好的选择。

另外,现在已经没有必要实现 SSE2 的 SIMD 代码了,因为 2015 年以后生产的大多数处理器都支持 AVX2。

CPU 特性检测

SIMD 加速代码依赖于 CPU 上可用的指令集。在 Rust 中有几种不同的方式来检测 CPU 特性。

运行时检测

使用 std::arch 模块提供的宏进行运行时检测:

fn foo() {
    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]

    {
        // 检测当前 CPU 是否支持 AVX2 指令集

        if
 is_x86_feature_detected!("avx2") {
            return
 unsafe { foo_avx2() };
        }
    }
    // 不使用 AVX2 的回退实现

}

编译时检测

使用条件编译进行编译时检测:

#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
fn
 optimized_function() {
    // AVX-512 优化实现

}

推荐的混合方案

建议默认提供运行时检测,同时提供 Cargo feature 让用户选择仅编译时检测:

# Cargo.toml
[features]

default
 = ["std"]
# 在支持的平台上启用标准库进行 CPU 特性检测

std
 = []
fn my_function() {
    // 使用运行时检测

    #[cfg(feature = "std")]

    {
        #[cfg(target_arch = "x86_64")]

        if
 is_x86_feature_detected!("avx512f") {
            return
 my_function_avx512();
        }

        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]

        if
 is_x86_feature_detected!("avx2") {
            return
 my_function_avx2();
        }
    }

    // 使用编译时检测

    #[cfg(not(feature = "std"))]

    {
        #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]

        return
 my_function_avx512();

        #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]

        return
 my_function_avx2();
    }

    // 当 SIMD 加速不可用时的标量回退实现

    return
 my_function_generic();
}

选择纯 Rust SIMD 实现方式

在纯 Rust 中使用 SIMD 指令有几种不同的方式:

1. 标准库的实验性 simd 模块

目前仅在 Rust nightly 版本中可用,但这是未来最令人期待的特性。

2. wide crate

一个第三方 crate,为稳定版 Rust 复制了 simd 模块的功能,但目前仅限于 256 位向量:

use wide::*;

fn
 main() {
    // 创建一个填充值为 1 的向量

    let
 a = u32x4::splat(1);
    // 从数组创建向量

    let
 b = u32x4::from([1, 2, 3, 4]);
    // 执行向量加法

    let
 result = a + b;
    assert_eq!
(result.to_array(), [2, 3, 4, 5]);
}

如果你不介意额外的依赖,这是推荐的方式。

3. pulp crate

一个高层次的 SIMD 抽象,可以理解为 SIMD 版的 rayon:

use pulp::Arch;

fn
 main() {
    let
 mut v = (0..1000).map(|i| i as f64).collect::<Vec<_>>();
    let
 arch = Arch::new();
    // 自动分发到最优的 SIMD 实现

    arch.dispatch(|| {
        for
 x in &mut v {
            *x *= 2.0;
        }
    });
    for
 (i, x) in v.into_iter().enumerate() {
        assert_eq!
(x, 2.0 * i as f64);
    }
}

4. 标准库的 arch 模块

这是最底层的方式,直接暴露原始的内联函数和向量寄存器类型。虽然代码重复较多,但这是目前唯一在稳定版 Rust 上无需任何依赖就能工作的方式。

关于自动向量化

编译器的自动向量化是一个重要的话题。对于一些常见操作,比如对两个缓冲区进行异或运算,编译器通常能够自动生成向量化代码:

// 编译器会自动识别这个模式并生成向量化代码
input_block
    .iter_mut()
    .zip(keystream)
    .for_each(|(plaintext, keystream)| *plaintext ^= *keystream);

建议是:除非你有确凿的证据证明某处是性能瓶颈,否则不要为常见操作手动实现 SIMD 优化。编译器很可能会为你自动向量化,或者至少输出高效的代码。

测试你的实现

别忘了使用不同的 SIMD 指令集来测试你的实现。可以使用 RUSTFLAGS 环境变量来选择性禁用 CPU 特性:

# 测试通用代码(无 SIMD 加速)
RUSTFLAGS="-C target-cpu=native -C target-feature=-avx2,-avx512f" make test

# 测试 AVX2 代码

RUSTFLAGS="-C target-cpu=native -C target-feature=-avx512f" make test

# 测试 AVX-512 代码

make test

需要注意的是,GitHub Actions 目前不支持 AVX-512 指令,因此你需要在自己的机器上运行 AVX-512 测试。

可移植 SIMD:未来可期

Rust 的可移植 SIMD(simd 模块)可能是目前 nightly 版本中最令人兴奋的特性之一。

它将极大地减轻开发者的维护负担,让我们只需为每种向量大小实现一次算法,编译器会在编译时选择适合不同 CPU 架构的具体指令,并自动回退到标量实现:

fn main() {
    // 为所有支持 128 位寄存器的平台创建一个 128 位向量

    let
 a = u32x4::splat(1);
    let
 b = u32x4::from([1, 2, 3, 4]);
    let
 result = a + b;
    assert_eq!
(result.to_array(), [2, 3, 4, 5]);
}

这意味着我们不再需要学习每个平台/向量大小的特定内联函数名称,代码也将大大简化。例如,作者之前需要为 ChaCha20 的 128 位向量实现编写两次代码——一次是 ARM64 的 NEON,一次是 wasm32 的 simd128。虽然代码几乎相同,只是类型和内联函数名称不同,但这意味着更多的代码需要测试、维护和文档化。

有了可移植 SIMD,只需要基于 u32x4 类型实现一次,Rust 就会将其编译为任何支持 128 位向量指令平台的优化代码。

总结

SIMD 编程是提升性能的利器,而 Rust 让这一切变得前所未有的简单:

  1. 1. Zen 5 等新一代 CPU 让 AVX-512 真正可用,不再有降频困扰
  2. 2. SIMD 编程遵循"加载 → 计算 → 存储"的三步模式
  3. 3. 选择合适的目标平台,服务器端关注 AVX-512,消费端关注 AVX2 和 NEON
  4. 4. 使用运行时检测结合编译时检测的混合方案
  5. 5. 对于常见操作,信任编译器的自动向量化
  6. 6. 期待可移植 SIMD 特性登陆稳定版 Rust

Rust 正在逐步吞噬整个计算栈,从微控制器到大型服务器,从 WebAssembly 到机器人和卫星。超过 37% 的密码学库漏洞是内存安全问题,C 和汇编正在逐渐退出历史舞台,而 Rust 是唯一合理的替代方案。

参考文章

  1. 1. SIMD programming in pure Rust: https://kerkour.com/introduction-rust-simd

书籍推荐

这本《Rust权威指南》中文版第二版是由 Rust 核心开发团队编写的权威学习资料,由中国 Rust 社区成员翻译。它适合所有希望评估、入门、提高和研究 Rust 语言的软件开发人员,被视为 Rust 开发工作的必读书目。

本书由浅入深地介绍了 Rust 语言的基础概念到独有实用工具的方方面面,涵盖了所有权、trait、生命周期、安全保证等高级概念,以及模式匹配、错误处理、包管理、函数式特性和并发机制等实用工具。书中包含三个完整的项目开发实战案例,手把手教读者从零开始开发 Rust 实践项目。

特别值得注意的是,本书已更新至 Rust 2021 版本内容,既能满足初学者系统学习需求,也适合有经验的开发者作为参考指南,是构建扎实 Rust 技能的最佳入口。

推荐阅读

  1.  Rust:横扫 C/C++/Go 的性能之王?

  2.  从 Rust 开发者视角看 C++:优缺点大揭秘

  3.  Rust vs Zig:新兴系统编程语言之争

  4.  Rust 异步编程必知设计模式:提升你的代码性能与可维护性

最新文章

随机文章