rust以其独特的安全性、速度和并发性组合而迅速流行。但是与其它任何语言一样,要充分利用rust需要的不仅仅是理解它的语法和习惯用法——还需要深入了解如何有效地利用和优化它的编译器。
为了说明这一点,我们设计了一个实际用例——一个actix web应用程序中的矩阵乘法任务。这种cpu密集型操作为分析各种编译器优化提供了一个完美的场景。
随着实验的深入,我们将调整cargo.toml文件的设置。利用特定的构建标志,甚至交换内存分配器。通过测量每次更改对性能的影响,我们将对rust的编译器优化有一个全面的了解。
实际用例
我们使用actix web开发了一个紧凑的应用程序,具有唯一的路由/matrix-multiplication。这个接口接收一个json数据,带有一个属性:n。
在接收到请求后,应用程序立即开始行动,动态地生成两个大小为n x n的矩阵,在矩阵中随机填充一些数据。然后将这些矩阵相乘在一起,将计算的结果返回给用户。
新建一个rust项目:
cargo new compiler-optimizations然后在cargo.toml文件中写入如下内容:[dependencies]anyhow = 1.0.71actix-web = 4.3.1dotenv = 0.15.0serde = { version = 1.0, features = [derive] }serde_json = 1.0.96log = 0.4.17env_logger = 0.10.0serde_derive = 1.0.163rand = 0.8.5mimalloc = { version = 0.1.37, default-features = false }[profile.release]lto = truecodegen-units = 1panic = abortstrip = true 在src/main.rs中写入如下代码:use std::env;use rand::rng;use actix_web::{app, get, post, httpresponse, httpserver, middleware, web};use anyhow::result;use serde::{deserialize, serialize};#[global_allocator]static global: mimalloc::mimalloc = mimalloc::mimalloc;#[derive(debug, clone, serialize, deserialize)]struct message { pub message: string,}#[derive(debug, clone, serialize, deserialize)]struct matrixsize { pub n: usize,}#[derive(debug, clone, serialize, deserialize)]struct matrixresult { pub matrix: vec,}#[get(/healthz)]async fn health() -> httpresponse { httpresponse::ok().json(message { message: healthy.to_string(), })}async fn not_found() -> httpresponse { httpresponse::notfound().json(message { message: not found.to_string(), })}#[post(/matrix-multiplication)]async fn matrix_multiplication(size: web::json) -> httpresponse { let n = size.n; let matrix_a = generate_random_matrix(n); let matrix_b = generate_random_matrix(n); let result = multiply_matrices(&matrix_a, &matrix_b); httpresponse::ok().json(matrixresult { matrix: result })}fn generate_random_matrix(n: usize) -> vec { let mut rng = rand::thread_rng(); (0..n).map(|_| (0..n).map(|_| rng.gen_range(0..n as i32)).collect()).collect()}fn multiply_matrices(matrix_a: &vec, matrix_b: &vec) -> vec { let a_rows = matrix_a.len(); let a_cols = matrix_a[0].len(); let b_cols = matrix_b[0].len(); let mut result = vec![vec![0; b_cols]; a_rows]; for i in 0..a_rows { for j in 0..b_cols { for k in 0..a_cols { result[i][j] += matrix_a[i][k] * matrix_b[k][j]; } } } result}#[actix_web::main]async fn main() -> result { env_logger::new().default_filter_or(info)); let port = env::var(port).unwrap_or_else(|_| 8080.to_string()); httpserver::new(move || { app::new() .wrap(middleware::default()) .service(health) .service(matrix_multiplication) .default_service(web::route().to(not_found)) }) .bind(format!(0.0.0.0:{}, port))? .run() .await.expect(failed to run server); ok(())}
优化设置
1,cargo.toml配置文件配置了-[profile.release]部分,用于调整优化性能。我们使用了以下优化设置:
lto = true:用于启用链路时间优化;
codegen-units = 1:即在整个crate中使用最高级别优化;
panic = abort:发生panic时调用abort而不是unwind;
strip = true:通过移除debug符号来减小二进制大小。
2,构建标识——通过设置rustflags= -c target-cpu=native ,我们可以确保编译器根据机器的特定架构来优化构建。
3,备用内存分配器——我们还尝试了mimalloc内存分配器,对于某些工作负载,它可以提供比默认分配器更好的性能特征。
测试
为了对actix web api进行负载测试,我们将使用一个功能强大但轻量级的工具——drill。
为了模拟高负载,我们的测试参数将包括两个场景中的500个并发请求——一个有10,000次迭代,另一个有20,000次迭代。这实际上分别达到了50,000和100,000个请求。
测试将在各种配置下进行,以获得全面的性能视图,如下所列:
1,cargo run :构建一个没有任何优化的开发版本(标记为“d”)。
2,cargo run --release:构建一个没有任何优化的发布版本(标记为“r”)。
3,rustflags=-c target-cpu=native cargo run --release:根据机器的特定架构来优化构建一个发布版本,(标记为“ropt”)。
4,与上一个命令一样,但是在代码中采用了mimalloc的内存分配器(表示为'roptmimalloc')。
结果
| build type | total time (s) | requests per second || --- | --- | --- || dev build unoptimized 50k | 71.3 | 701.45 || release build unoptimized 50k | 27.0 | 1849.95 || release build optimized (flags) 50k | 25.8 | 1937.80 || release build optimized (flags + mimalloc) 50k | 26.7 | 1873.65 || release build unoptimized 100k | 52.1 | 1918.27 || release build optimized (flags) 100k | 51.7 | 1934.59 || release build optimized (flags + mimalloc) 100k | 51.1 | 1955.07 |
从50k请求测试开始,未优化的开发构建每秒能够处理大约701.45个请求,但是当代码在发布模式下编译时,每秒的请求飙升到1849.95个。这展示了rust编译器在从开发模式切换到发布模式时所产生的显著差异。
使用针对本机cpu架构的构建标志添加优化,进一步提高了性能,达到每秒1937.80个请求。
当我们加入mimalloc(备用内存分配器)时,每秒请求数略微下降到1873.65。这表明,虽然mimalloc可以提高内存使用效率,但它不一定能在每个场景中都能提高请求处理速度。
转到100k个请求测试,有趣的是,未优化版本和优化版本之间的性能差异不那么明显。未优化的版本实现了每秒1918.27个请求,而优化的版本(带和不带mimalloc)分别达到了每秒1934.59和1955.07个请求。
这表明,当处理大量请求时,我们优化的影响变得不那么明显。尽管如此,即使在更重的负载下,构建优化仍然能提供最佳性能。
介绍assert的使用方法
影驰GTX1070骨灰大将怎么样?2K游戏小意思
为同步整流选择最优化的MOSFET
魅族Note8评测 品质与性价比兼得
软银孙正义如何成为美国硅谷最有权势之人?
最大化Rust性能:编译器优化的比较分析
直流、射频和光学探头定位器,可实现最高精度的测量
申请函数kmalloc、kzalloc、vmalloc区别说明
移动医疗2017年将迎来新拐点 盈利或有望
三星Galaxy S21系列软件界面曝光
公交wifi变现难 16WiFi被迫关停十几个城市业务
老式ISA设备的WDM驱动程序的开发与实现
2012财富美国500强:苹果利润仅次于埃克森美孚
美格智能高算力智能模组SNM951——游戏“上云”,一秒即应
FM25CL64B-GTR是一款串行FRAM存储器
小米6放弃骁龙835和超声波指纹,上自家高端松果!
2020年二季度5G智能手机占全球手机出货量的10%
夏普液晶电视技术的详细介绍
“反恐爬壁机器人”到底如何爬壁?
一文详解pcb涨缩标准是多少