在开发者的普遍印象中,Go 常常被视为在性能和开发效率之间取得平衡的语言,而 Rust 则被奉为性能的终极追求者,尤其在 AES 这类重度依赖 CPU 计算的加密场景下。本文将通过一次严谨的性能测试,挑战这一固有认知。我们将对比 Go 语言(使用标准库、自定义实现及汇编优化)与 Rust 语言在 AES-256-CBC 加密算法上的性能表现,并揭示在不同硬件架构下的结果差异。
一、测试环境与方法
为确保测试的客观性,我们在两个不同的硬件平台上进行了对比。
1.1 硬件与系统环境
AMD64 平台(x86_64):
- 服务器搭载 Intel(R) Xeon(R) CPU E5-2609 @ 2.40GHz 处理器,支持 AES-NI 指令集。
- 操作系统为 Linux,内核版本
5.14.0-803.38.1.el9_5.x86_64。
- 内存总计约 16GB。
ARM64 平台(Apple Silicon):
- 个人电脑为 Apple M3 芯片的 MacBook Pro(14英寸,2023年11月)。
- 内存为 16 GB。
1.2 语言环境
Go 环境:
- 在 ARM64 平台上使用
go1.25.1 darwin/arm64。
- 在 AMD64 平台上使用相应版本的 Go 工具链。
Rust 环境:
- 在 ARM64 平台上,
cargo 版本为 1.93.0,活动的 rustc 编译器版本为 1.93.0,工具链为 stable-aarch64-apple-darwin。
- 在 AMD64 平台上,
cargo 版本为 1.92.0,活动的 rustc 编译器版本为 1.92.0,工具链为 stable-x86_64-unknown-linux-gnu。
1.3 测试数据与方法
测试前,我们生成了两个测试数据文件:
- 一个在 macOS 上,名为
aestest100000.data,大小为 103MB。
- 另一个在 Linux 服务器上,名为
aetest100w.data,大小为 1.1GB。
文件内容格式为:奇数行是待加密的明文数据(长度在 47 到 2048 字节之间随机),偶数行是固定的 32 字节加密密码。
测试程序的任务是读取文件,循环处理每一对“密码-明文”数据,进行 AES-256-CBC 加密,并统计总耗时与平均每次加密耗时。文件 IO 时间不计入性能统计。每个测试程序在各自平台上连续运行 5 次,取平均成绩作为最终结果。
二、核心代码实现
2.1 Rust 实现
Rust 端使用了 aes 和 cbc 等 crate 进行标准库加密。核心加密函数旨在复用缓冲区以减少内存分配。
加密函数 aes256_cbc_encrypt_reuse_buffer:
// AES-256-CBC 加密
// 复用 buffer,返回密文长度
fn aes256_cbc_encrypt_reuse_buffer(
password: &[u8],
plaintext: &[u8],
buffer: &mut Vec<u8>,
) -> usize {
let key: &[u8; 32] = password[..32].try_into().unwrap();
let iv: &[u8; 16] = password[..16].try_into().unwrap();
buffer.clear();
buffer.extend_from_slice(plaintext);
let block_size: usize = 16;
let pad_len = block_size - (buffer.len() % block_size);
buffer.resize(buffer.len() + pad_len, pad_len as u8);
let cipher: Encryptor<Aes256> = Encryptor::<Aes256>::new(key.into(), iv.into());
cipher.encrypt_padded_mut::<Pkcs7>(buffer, plaintext.len()).unwrap();
buffer.len()
}
性能测试与资源统计主函数:
fn main() -> io::Result<()> {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
std::process::exit(1);
}
let file_path: &? = &args[1];
let pid: Pid = sysinfo::get_current_pid().unwrap();
let mut sys: System = System::new_all();
sys.refresh_processes();
encrypt_file_stream_and_profile01(file_path).unwrap();
sys.refresh_processes();
if let Some(proc: &Process) = sys.process(pid) {
println!("=== Process Resource Usage ===");
println!("Memory : {} KB", proc.memory()/1024);
println!("CPU : {:} %", proc.cpu_usage());
println!("==============================");
}
Ok(())
}
2.2 Go 实现
Go 语言方面,我们准备了三种实现进行对比:
- 标准库实现:使用
crypto/aes 和 crypto/cipher。
- 自定义实现:优化内存布局,减少拷贝,直接操作指针。
- 汇编优化实现:调用手写的汇编函数,一次性处理多个数据块。
Go 标准库实现核心片段:
tmpTime01 = time.Now().UnixNano()
tmpBlock, err = aes.NewCipher(tmpReadData[pwdDataIdx[0]:pwdDataIdx[1]])
if err == nil {
rawDataLen = rawDataIdx[1] - rawDataIdx[0]
padding = AesSecBlockSize - rawDataLen%AesSecBlockSize
padByte = byte(padding)
tmpResultLen = rawDataLen + padding
copy(tmpReadData[pwdDataIdx[0]+16:pwdDataIdx[1]], tmpReadData[pwdDataIdx[0]:pwdDataIdx[0]+16])
for tmpA = rawDataIdx[1]; tmpA < (rawDataIdx[1] + padding); tmpA++ {
tmpReadData[tmpA] = padByte
}
tmpBlockMode = cipher.NewCBCEncrypter(tmpBlock, tmpReadData[pwdDataIdx[0]+16:pwdDataIdx[1]])
tmpBlockMode.CryptBlocks(resultBuf[:tmpPageSize], tmpReadData[rawDataIdx[0]:rawDataIdx[0]+tmpResultLen])
totalEncryptedLen += tmpResultLen
}
tmpTime02 = time.Now().UnixNano()
totalTime += tmpTime02 - tmpTime01
Go 自定义实现核心函数 EncryptWithBlocks:
// EncryptWithBlocks 数据CBC模式加密直接通过Block 支持原地加密
func EncryptWithBlocks(blockPtr, dstDataPtr, srcDataPtr uintptr, ivDataPtr uintptr, srcLen int) {
var i int
var tmpData [2]uint64
var tmpDataPtr uintptr
var srcPtr *[2]uint64
var ivPtr *[2]uint64
var oldIv [2]uint64
oldIv = (*[2]uint64)(unsafe.Pointer(ivDataPtr))
ivPtr = (*[2]uint64)(unsafe.Pointer(ivDataPtr))
tmpDataPtr = uintptr(unsafe.Pointer(&tmpData[0]))
for i = 0; i < srcLen; i += 16 {
srcPtr = (*[2]uint64)(unsafe.Pointer(srcDataPtr))
tmpData[0] = srcPtr[0] ^ ivPtr[0]
tmpData[1] = srcPtr[1] ^ ivPtr[1]
aes.NewEncryptSitk(blockPtr, dstDataPtr, tmpDataPtr)
ivPtr = (*[2]uint64)(unsafe.Pointer(dstDataPtr))
dstDataPtr += 16
srcDataPtr += 16
}
(*[2]uint64)(unsafe.Pointer(ivDataPtr)) = oldIv
}
Go 汇编优化接口函数:
package main
import "unsafe"
// 声明汇编函数
//go:noescape
func encryptCBCAsm(xk *uint32, dst, src, iv *byte, blocks int)
/* EncryptWithBlocksOptimized 优化后的 CBC 加密
*/
func EncryptWithBlocksOptimized(xkPtr *uint32, dstDataPtr, srcDataPtr, ivDataPtr uintptr, srcLen int) int {
if srcLen <= 0 || srcLen%16 != 0 {
return 0
}
blocks := srcLen / 16
// 直接调用汇编,一次性处理所有 Blocks
encryptCBCAsm(
xkPtr,
(*byte)(unsafe.Pointer(dstDataPtr)),
(*byte)(unsafe.Pointer(srcDataPtr)),
(*byte)(unsafe.Pointer(ivDataPtr)),
blocks,
)
return blocks * 16
}
Go 的测试主程序通过命令行参数选择不同的加密实现,并统计进程资源使用情况。
switch os.Args[1] {
case "1":
fmt.Println("Test AES CBC With Std Crypt method!")
_ = EncryptFileStream01(os.Args[2])
case "2":
fmt.Println("Test AES CBC With Custom Crypt method!")
_ = EncryptFileStream02(os.Args[2])
case "3":
fmt.Println("Test AES CBC With Custom ASM method!")
_ = EncryptFileStream03(os.Args[1])
}
三、性能测试结果分析
所有实现均通过结果验证,加密输出一致,确保性能对比的有效性。
3.1 ARM64 (Apple M3) 平台结果
在该平台下,不同实现的平均单次加密耗时对比如下:
- Rust 标准库:17585.6 纳秒
- Go 标准库:1246.8 纳秒
- Go 自定义实现:1185.9 纳秒
- Go 汇编优化:857.6 纳秒
分析:结果令人震惊。Rust 在此平台上的表现异常缓慢,耗时远超 Go 的任何一种实现。这通常意味着 Rust 所使用的加密库在 ARM64 架构下可能未能有效利用硬件加速指令(如 ARMv8 的加密扩展),或者存在较大的抽象开销。相比之下,Go 的表现则正常得多,其标准库已具备良好的性能,而经过汇编优化后,性能进一步提升,达到全场最佳的 857.6 纳秒,比 Rust 快了超过 20 倍。
3.2 AMD64 (Intel Xeon) 平台结果
在 x86_64 架构下,测试结果呈现不同的态势:
- Rust 标准库:3861.6 纳秒
- Go 标准库:4415.1 纳秒
- Go 自定义实现:约 3600 纳秒(根据上下文估算)
- Go 汇编优化:3456.0 纳秒
分析:在 Rust 的传统优势战场 x86 平台上,其标准库确实展现了强大的性能,以 3861.6 ns 的成绩领先于 Go 标准库的 4415.1 ns,领先幅度约 14%。然而,当 Go 启用自定义的汇编优化后,情况发生逆转。汇编优化版本将耗时压低至 3456.0 ns,成功反超 Rust 标准库实现,性能领先约 10.5%。这证明了在成熟的 x86 平台上,通过精细化的底层优化,Go 完全有能力挑战甚至超越以性能著称的 Rust。
四、结论与启示
本次测试带来了几个颠覆性的发现:
- 平台依赖性极强:加密性能严重依赖于硬件架构和底层库的实现。Rust 在 AMD64 上的优秀表现并未延续到 ARM64,而 Go 在两个平台上都表现出了更一致的性能基线。
- 汇编优化的威力:无论是 ARM64 还是 AMD64,手写汇编优化都带来了显著的性能提升。在 AMD64 上,它帮助 Go 实现了对 Rust 的 10.5% 反超;在 ARM64 上,则将性能推至极致。这揭示了在追求极限性能的场景下,深入底层的重要性。
- 破除“语言决定论”:测试结果有力地表明,“没有慢的语言,只有未充分优化的实现”。Go 通过合理的后端架构与底层优化,在特定的加密任务上可以超越 Rust。性能的差异更多源于编译器优化、标准库实现以及最终生成的机器代码质量,而非语言本身的绝对优劣。
对于高吞吐量、低延迟的加密应用开发者而言,选择技术栈时需紧密结合目标部署平台进行性能评估。如果目标环境是 ARM 服务器,Go 可能是更安全、性能更可预测的选择。如果在 x86 环境且团队具备底层优化能力,Go 同样可以通过优化达到顶尖水平。本次测试也说明,深入的技术探索和社区交流,例如在 云栈社区 这样的平台上分享与学习,是推动技术选型和实践进步的关键。
参考资料
[1] 谁说Golang性能不如Rust?在 AES CBC加密上,Go反超Rust 10%!, 微信公众号:mp.weixin.qq.com/s/I2x-PkRL98PCdEqp_38jhw
版权声明:本文由 云栈社区 整理发布,版权归原作者所有。