如何對 Rust web 應用程序做性能分析 - 2 火焰圖
在這篇文章中, 我們使用 cargo-flamegraph 工具對上一篇文章的 Rust web 應用程序進行分析,並繪製火焰圖。
火焰圖
基本思想是,使用 perf 或 dtrace 等工具收集性能數據,特別是在採樣期間,哪些函數佔用了多少 CPU 時間,然後以一種可以很好理解的方式將結果可視化。
火焰圖還可以用於分析 Off-CPU,例如,它可以幫助查找線程頻繁等待 I/O 的問題。
在本例中,我們將只做 CPU 時間分析,這是由 cargo-flamegraph 支持的。
安裝 cargo-flamegraph:
cargo install flamegraph
首先,讓我們在 handler.rs 中增加如下代碼:
1pub async fn cpu_handler_alloc(clients: FasterClients) -> WebResult<impl Reply> {
2 let clients_lock = clients.read().await;
3 let user_ids: Vec<String> = clients_lock
4 .iter()
5 .map(|(_, client)| client.user_id.to_string())
6 .collect();
7 drop(clients_lock);
8
9 let mut result = 0;
10 for i in 0..1000000 {
11 result += user_ids
12 .iter()
13 .cloned()
14 .rev()
15 .map(|user_id| user_id.parse::<usize>().expect("can be parsed to usize"))
16 .fold(i, |acc, x| acc + x);
17 }
18 Ok(reply::html(result.to_string()))
19}
這也是個精心設計的示例,我們重用了 / fast 處理程序,但我們將計算擴展爲在一個長循環中運行。還要注意我們在迭代器上使用. clone(),每次迭代都克隆了整個列表。這是一個相當明顯的性能問題,在運行時,這會導致性能問題。
接下來在 main.rs 中增加如下代碼:
1let cpu_route_alloc = warp::path!("cpualloc")
2 .and(with_faster_clients(faster_clients.clone()))
3 .and_then(handler::cpu_handler_alloc);
4
5println!("Started server at localhost:8080");
6warp::serve(read_route.or(fast_route).or(cpu_route_alloc))
7 .run(([0, 0, 0, 0], 8080))
8 .await;
此外,我們在 / locust 目錄中添加了另一個名爲 cpu.py 的 Locust 文件:
from locust import HttpUser, task, between
class Basic(HttpUser):
wait_time = between(0.5, 0.5)
@task
def cpu(self):
self.client.get("/cpualloc")
這在本質上與之前相同,只是調用 / cpualloc 接口。
讓我們使用以下命令運行 cargo flamegraph 來收集分析數據:
sudo cargo flamegraph --bin rust-web-profiling-example
在項目根目錄下生成 flamegraph.svg 文件。
在我們的應用程序上運行上篇文章介紹的負載測試並使用 CTRL+C 停止運行 Rust web 服務器後,我們得到這樣一個火焰圖:
可以看到這種可視化的好處,我們可以從 Tokio 運行時一直跟蹤到 cpu_handler 和計算過程。需要注意的一點是,我們花了很多時間做分配。
這並不奇怪,因爲我們在迭代器中添加了. clone(),對於每次循環迭代,它都會在處理數據之前克隆列表的內容。我們可以看到,在分配塊之間,我們還花了一些時間將字符串解析爲數字。
修復這個問題非常簡單,我們只需刪除. clone(),因爲我們在這裏根本不需要它,但你可能已經注意到,不必要的克隆會導致很大的性能影響,特別是在熱代碼中。
在 src/handler.rs 中增加如下代碼:
1pub async fn cpu_handler(clients: FasterClients) -> WebResult<impl Reply> {
2 let clients_lock = clients.read().await;
3 let user_ids: Vec<String> = clients_lock
4 .iter()
5 .map(|(_, client)| client.user_id.to_string())
6 .collect();
7 drop(clients_lock);
8
9 let mut result = 0;
10 for i in 0..1000000 {
11 result += user_ids
12 .iter()
13 .rev()
14 .map(|user_id| user_id.parse::<usize>().expect("can be parsed to usize"))
15 .fold(i, |acc, x| acc + x);
16 }
17 Ok(reply::html(result.to_string()))
18}
在 src/main.rs 中增加如下代碼:
1let cpu_route = warp::path!("cpu")
2 .and(with_faster_clients(faster_clients.clone()))
3 .and_then(handler::cpu_handler);
4
5println!("Started server at localhost:8080");
6warp::serve(read_route.or(fast_route).or(cpu_route_alloc).or(cpu_route))
7 .run(([0, 0, 0, 0], 8081))
8 .await;
我們再進行一次抽樣。我們會得到這樣的火焰圖:
差別很大啊!我們花在分配內存上的時間少了很多,而把大部分時間花在將字符串解析爲數字和計算結果上。
你還可以使用 Hotspot 等工具來創建和分析火焰圖。
本文翻譯自:
https://blog.logrocket.com/an-introduction-to-profiling-a-rust-web-application/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/nphBaY9Bms9Tw2gEGGgqNg