Rust 性能提升“最后一公里”:詳解 Profiling 瓶頸定位與優化|得物技術

一、Profiling:揭示性能瓶頸的“照妖鏡”

在過去的一年里,我們團隊完成了一項壯舉:將近萬核的 Java 服務成功遷移到 Rust,并收獲了令人矚目的性能提升。我們的實踐經驗已在《RUST練習生如何在生產環境構建萬億流量》一文中與大家分享。然而,在這次大規模遷移中,我們觀察到一個有趣的現象:大多數服務在遷移后性能都得到了顯著提升,但有那么一小部分服務,性能提升卻不盡如人意,僅僅在 10% 左右徘徊。

這讓我們感到疑惑。明明已經用上了性能“王者”Rust,為什么還會遇到瓶頸?為了解開這個謎團,我們決定深入剖析這些“低提升”服務。今天,我就來和大家分享,我們是如何利用?Profiling?工具,找到并解決寫入過程中的性能瓶頸,最終實現更高性能飛躍的!

在性能優化領域,盲目猜測是最大的禁忌。你需要一把鋒利的“手術刀”,精準地找到問題的根源。在 Rust 生態中,雖然不像 Java 社區那樣擁有 VisualVM 或 JProfiler 這類功能強大的成熟工具,但我們依然可以搭建一套高效的性能分析體系。

為了在生產環境中實現高效的性能監控,我們引入了?Jemalloc?內存分配器和?pprof?CPU 分析器。這套方案不僅支持定時自動生成 Profile 文件,還可以在運行時動態觸發,極大地提升了我們定位問題的能力。

二、配置項目:讓Profiling“武裝到牙齒”

首先,我們需要在?Cargo.toml?文件中添加必要的依賴,讓我們的 Rust 服務具備 Profiling 的能力。以下是我們的配置,Rust 版本為 1.87.0。

[target.'cfg(all(not(target_env = "msvc"), not(target_os = "windows")))'.dependencies]
# 使用 tikv-jemallocator 作為內存分配器,并啟用性能分析功能
tikv-jemallocator?= { version =?"0.6", features = ["profiling",?"unprefixed_malloc_on_supported_platforms"] }
# 用于在運行時控制和獲取 jemalloc 的統計信息
tikv-jemalloc-ctl?= { version =?"0.6", features = ["use_std",?"stats"] }
# tikv-jemallocator 的底層綁定,同樣啟用性能分析
tikv-jemalloc-sys?= { version =?"0.6", features = ["profiling"] }
# 用于生成與 pprof 兼容的內存剖析數據,并支持符號化和火焰圖
jemalloc_pprof?= { version =?"0.7", features = ["symbolize","flamegraph"] }
# 用于生成 CPU 性能剖析數據和火焰圖
pprof?= { version =?"0.14", features = ["flamegraph",?"protobuf-codec"] }

簡單來說,這幾個依賴各司其職:

※?tikv-jemallocator

基于?jemalloc?的 Rust 實現,以其高效的內存管理聞名。

※?jemalloc_pprof

負責將?jemalloc?的內存剖析數據轉換成標準的 pprof 格式。

※?pprof

用于 CPU 性能分析,可以生成?pprof?格式的 Profile 文件。

三、? 全局配置:啟動Profiling開關

接下來,在?main.rs?中進行全局配置,指定?Jemalloc?的?Profiling?參數,并將其設置為默認的全局內存分配器。

// 配置 Jemalloc 內存分析參數
#[export_name =?"malloc_conf"]
pub static malloc_conf: &[u8] = b"prof:true,prof_active:true,lg_prof_sample:16\0";#[cfg(not(target_env =?"msvc"))]
use tikv_jemallocator::Jemalloc;// 將 Jemalloc 設置為全局內存分配器
#[cfg(not(target_env =?"msvc"))]
#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc;

這段配置中的?lg_prof_sample:16?是一個關鍵參數。

它表示?jemalloc?會對大約每?2^16?字節(即 64KB)的內存分配進行一次采樣。這個值越大,采樣頻率越低,內存開銷越小,但精度也越低;反之則精度越高,開銷越大。在生產環境中,我們需要根據實際情況進行權衡。

四、實現Profile生成函數:打造你的“數據采集器”

我們將 Profile 文件的生成邏輯封裝成異步函數,這樣就可以在服務的任意時刻按需調用,非常靈活。

內存Profile生成函數

#[cfg(not(target_env =?"msvc"))]
async?fn?dump_memory_profile() ->?Result<String,?String> {// 獲取 jemalloc 的 profiling 控制器let?prof_ctl =?jemalloc_pprof::PROF_CTL.as_ref().ok_or_else(||?"Profiling controller not available".to_string())?;let?mut prof_ctl = prof_ctl.lock().await;// 檢查 profiling 是否已激活if?!prof_ctl.activated() {return?Err("Jemalloc profiling is not activated".to_string());}// 調用 dump_pprof() 方法生成 pprof 數據let?pprof_data = prof_ctl.dump_pprof().map_err(|e| format!("Failed to dump pprof: {}", e))?;// 使用時間戳生成唯一文件名let?timestamp =?chrono::Utc::now().format("%Y%m%d_%H%M%S");let?filename = format!("memory_profile_{}.pb", timestamp);// 將 pprof 數據寫入本地文件std::fs::write(&filename, pprof_data).map_err(|e| format!("Failed to write profile file: {}", e))?;info!("Memory profile dumped to: {}", filename);Ok(filename)
}

CPU Profile生成函數

類似地,我們使用?pprof?庫來實現 CPU?Profile?的生成。

#[cfg(not(target_env = "msvc"))]
async fn dump_cpu_profile() -> Result<String, String> {use?pprof::ProfilerGuard;use?pprof::protos::Message;info!("Starting CPU profiling for 60 seconds...");//?創建 CPU profiler,設置采樣頻率為?100?Hzlet guard = ProfilerGuard::new(100).map_err(|e|?format!("Failed to create profiler: {}", e))?;//?持續采樣?60?秒tokio::time::sleep(std::time::Duration::from_secs(60)).await;//?生成報告let report = guard.report().build().map_err(|e|?format!("Failed to build report: {}", e))?;//?使用時間戳生成文件名let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");let filename =?format!("cpu_profile_{}.pb", timestamp);//?創建文件并寫入 pprof 數據let mut file = std::fs::File::create(&filename).map_err(|e|?format!("Failed to create file: {}", e))?;report.pprof().map_err(|e|?format!("Failed to convert to pprof: {}", e))?.write_to_writer(&mut file).map_err(|e|?format!("Failed to write profile: {}", e))?;info!("CPU profile dumped to: {}", filename);Ok(filename)
}
  • ?ProfilerGuard::new()???100??Hz 意味著每秒鐘會隨機中斷程序?100 次,以記錄當前正在執行的函數調用棧
  • tokio::time::sleep(std::time::Duration::from_secs(60)).await 表示?pprof?將會持續采樣 60 秒鐘
  • ?guard.report().build()?這個方法用于將收集到的所有采樣數據進行處理和聚合,最終生成一個?Report?對象。這個?Report?對象包含了所有調用棧的統計信息,但還沒有轉換成特定的文件格式
  • ?report.pprof()?這是?Report?對象的一個方法,用于將報告數據轉換成?pprof?格式

五、?觸發和使用 Profiling:隨時隨地捕捉性能數據

有了上述函數,我們實現了兩種靈活的觸發方式。

※?定時自動生成

通過異步定時任務,每隔一段時間自動調用?dump_memory_profile()?和 ?dump_cpu_profile()?。

fn start_profilers() {//?Memory?profilertokio::spawn(async {let mut interval = tokio::time::interval(std::time::Duration::from_secs(300));loop {interval.tick().await;#[cfg(not(target_env = "msvc"))]{info!("Starting memory profiler...");match dump_memory_profile().await {Ok(profile_path) => info!("Memory profile dumped successfully: {}", profile_path),Err(e) => info!("Failed to dump memory profile: {}", e),}}}});//?同理可以實現CPU?profiler
}

※?手動 HTTP 觸發

通過提供?/profile/memory?和?/profile/cpu?兩個 HTTP 接口,可以隨時按需觸發 Profile 文件的生成。

async fn trigger_memory_profile() ->?Result<impl warp::Reply, std::convert::Infallible> {#[cfg(not(target_env = "msvc"))]{info!("HTTP triggered memory profile dump...");match dump_memory_profile().await {Ok(profile_path) =>?Ok(warp::reply::with_status(format!("Memory profile dumped successfully: {}", profile_path),warp::http::StatusCode::OK,)),Err(e) =>?Ok(warp::reply::with_status(format!("Failed to dump memory profile: {}", e),warp::http::StatusCode::INTERNAL_SERVER_ERROR,)),}}
}
//同理也可實現trigger_cpu_profile()函數
fn profile_routes() -> impl?Filter<Extract?= impl?Reply,?Error?= warp::Rejection> +?Clone?{let memory_profile = warp::post().and(warp::path("profile")).and(warp::path("memory")).and(warp::path::end()).and_then(trigger_memory_profile);let cpu_profile = warp::post().and(warp::path("profile")).and(warp::path("cpu")).and(warp::path::end()).and_then(trigger_cpu_profile);memory_profile.or(cpu_profile)
}

現在,我們就可以通過?curl?命令,隨時在生產環境中采集性能數據了:

curl -X POST http://localhost:8080/profile/memory
curl -X POST http://localhost:8080/profile/cpu

生成的?.pb?文件,我們就可以通過?go tool pprof?工具,啟動一個交互式 Web UI,在瀏覽器中直觀查看調用圖、火焰圖等。

go tool pprof -http=localhost:8080 ./target/debug/otel-storage ./otel_storage_cpu_profile_20250806_032509.pb

六、性能剖析:火焰圖下的“真相”

通過?go tool pprof?啟動的 Web UI,我們可以看到程序的火焰圖

如何閱讀火焰圖

※ 頂部:代表程序的根函數。

※ 向下延伸;子函數調用關系。

※ 火焰條的寬度:代表該函數在 CPU 上消耗的時間。寬度越寬,消耗的時間越多,越可能存在性能瓶頸

CPU Profile

Memory Profile

在我們的 CPU 火焰圖中,一個令人意外的瓶頸浮出水面:OSS::new?占用了約 19.1% 的 CPU 時間。深入分析后發現,?OSS::new?內部的?TlsConnector?在每次新建連接時都會進行 TLS 握手,這是導致 CPU 占用過高的根本原因。

原來,我們的代碼在每次寫入 OSS 時,都會新建一個 OSS 實例,隨之而來的是一個全新的 HTTP 客戶端和一次耗時的 TLS 握手。盡管?oss-rust-sdk?內部有連接池機制,但由于我們每次都創建了新實例,這個連接池根本無法發揮作用!

七、優化方案:從“每次新建”到“共享復用”

問題的核心在于重復創建 OSS 實例。我們的優化思路非常清晰:復用 OSS 客戶端實例,避免不必要的 TLS 握手開銷

優化前

每次寫入都新建 OSS 客戶端。

fn?write_oss() {// 每次寫入都新建一個OSS實例let?oss_instance =?create_oss_client(oss_config.clone());tokio::spawn(async?move {// 獲取寫入偏移量、文件名// 構造OSS寫入所需資源和頭信息// 寫入OSSlet?result = oss_instance.append_object(data, file_name, headers, resources).await;
}
fn?create_oss_client(config:?OssWriteConfig) ->?OSS?{OSS::new(……)
}

這種方案在流量較小時可能問題不大,但在萬億流量的生產環境中,頻繁的實例創建會造成巨大的性能浪費。

優化前

※ 共享實例

讓每個處理任務(?DecodeTask?)持有?Arc<OSS>?共享智能指針,確保所有寫入操作都使用同一個 OSS 實例。

let oss_client =?Arc::new(create_oss_client(oss_config.clone()));
let oss_instance =?self.oss_client.clone();?
// ...
let result = oss_instance.append_object(data, file_name, headers, resources).await;

※ 自動重建機制

為了應對連接失效或網絡問題,我們引入了自動重建機制。當寫入次數達到閾值或發生寫入失敗時,我們會自動創建一個新的 OSS 實例來替換舊實例,從而保證服務的健壯性。

// 使用原子操作確保多線程環境下的計數安全
let write_count =?self.oss_write_count.load(std::sync::atomic::Ordering::SeqCst);
let failure_count =?self.oss_failure_count.load(std::sync::atomic::Ordering::SeqCst);// 檢查是否需要重建實例...
fn?recreate_oss_client(&mut?self)?{let new_oss_client =?Arc::new(create_oss_client(self.oss_config.clone()));self.oss_client = new_oss_client;self.oss_write_count.store(0, std::sync::atomic::Ordering::SeqCst);self.oss_failure_count.store(0, std::sync::atomic::Ordering::SeqCst);// 記錄OSS客戶端重建次數指標OSS_CLIENT_RECREATE_COUNT.with_label_values(&[]).inc();info!("OSS client recreated");
}

八、優化效果:性能數據“一飛沖天”

優化后的服務上線后,我們觀察到了顯著的性能提升。

CPU 資源使用率

同比下降約?20%

OSS 寫入耗時

同比下降約?17.2%,成為集群中最短的寫入耗時。

※?OSS寫入耗時

※?OSS相關資源只占千分之一

內存使用率

平均下降?8.77%,這部分下降可能也得益于我們將內存分配器從?mimalloc?替換為?jemalloc?的綜合效果。

這次優化不僅解決了特定服務的性能問題,更重要的是,它驗證了在 Rust 中通過 Profiling 工具進行深度性能分析的可行性。即使在已經實現了初步性能提升的 Rust 服務中,仍然存在巨大的優化空間。

未來,我們將繼續探索更高效的 Profiling 方案,并深入挖掘其他潛在的性能瓶頸,以在萬億流量的生產環境中實現極致的性能和資源利用率。

?引用?

  • GitHub - tikv/jemallocator: Rust allocator using jemalloc as a backend
  • https://crates.io/crates/jemalloc_pprof
  • GitHub - google/pprof: pprof is a tool for visualization and analysis of profiling data
  • Use Case: Heap Profiling
  • https://jemalloc.net/jemalloc.3.html#heap_profile_format
  • https://www.brendangregg.com/flamegraphs.html
  • https://magiroux.com/rust-jemalloc-profiling

往期回顧

1.Valkey 單點性能比肩 Redis 集群了?Valkey8.0 新特性分析|得物技術

2.Java volatile 關鍵字到底是什么|得物技術

3.社區搜索離線回溯系統設計:架構、挑戰與性能優化|得物技術

4.正品庫拍照PWA應用的實現與性能優化|得物技術

5.得物社區活動:組件化的演進與實踐

文 /?炯帆?南風

關注得物技術,每周更新技術干貨

要是覺得文章對你有幫助的話,歡迎評論轉發點贊~

未經得物技術許可嚴禁轉載,否則依法追究法律責任。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/92967.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/92967.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/92967.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

STM32H5 的 PB14 引腳被意外拉低的問題解析 LAT1542

關鍵字&#xff1a;STM32H5&#xff0c; GPIO 1. 問題現象 客戶反饋&#xff0c;使用 STM32H523RET6 應用中配置了兩個 IO 口&#xff0c;PC9 為輸出模式&#xff0c;內部下拉&#xff1b;PB14 為輸入模式&#xff0c;內部上拉。在程序中將 PC9 引腳輸出高電平&#xff0c;結…

【辦公自動化】如何使用Python讓Word文檔處理自動化?

在日常辦公中&#xff0c;Word文檔是最常用的文本處理工具之一。通過Python自動化Word文檔操作&#xff0c;可以大幅提高工作效率&#xff0c;減少重復勞動&#xff0c;特別適合批量生成報告、合同、簡歷等標準化文檔。本文將介紹幾種常用的Python操作Word文檔的方法&#xff0…

順序表的總結及模擬實現

目錄 一.線性表 二.順序表 1.概念 2.結構 3.要實現的接口函數 三.模擬實現順序表 1.定義出順序表的基本結構 2.實現檢查擴容功能 3.實現尾插 4.實現尾刪 5.實現頭插和頭刪 6.查找 7.修改 8.遍歷 9.在指定位置插入和刪除 四.順序表的優缺點及思考 a.順序表的弊端 …

Vue3 vs Vue2:全面對比與面試寶典

文章目錄Vue3 vs Vue2&#xff1a;全面對比與面試寶典引言&#xff1a;Vue框架的進化之路一、核心架構對比二、響應式系統的革命Vue2的響應式&#xff1a;像老式監控攝像頭Vue3的響應式&#xff1a;像智能AI監控系統三、API風格的進化Vue2的Options API&#xff1a;像填表格Vue…

Java Web開發:Session與Cookie詳細入門指南

在Web開發中&#xff0c;狀態管理是核心需求之一。本文將深入講解Java中Session和Cookie的使用方法&#xff0c;幫助你掌握用戶狀態管理的核心技術。 一、Session與Cookie基礎概念 特性SessionCookie存儲位置服務器內存/持久化存儲客戶端瀏覽器安全性較高&#xff08;敏感數據…

HTTPS與CA證書:安全通信全解析

CA&#xff08;Certificate Authority&#xff09;&#xff1a;證書頒發機構&#xff0c;負責簽發和管理數字證書&#xff0c;驗證證書持有者的身份。HTTPS&#xff1a;基于 SSL/TLS 協議的 HTTP&#xff0c;通過證書實現客戶端與服務器的身份驗證和數據加密。HTTPSHTTPSSL/TLS…

AI生成代碼時代的商業模式重構:從“軟件即產品”到“價值即服務”

2025年,全球AI代碼生成市場規模突破63億元(數據來源:《中國AI代碼生成行業發展報告》),開發者效率提升40%以上,軟件開發成本下降30%。這一技術浪潮正在顛覆傳統軟件行業的商業邏輯——當代碼生成變得像文字編輯一樣簡單時,企業如何構建可持續的商業模式? 本文將從硬件…

C#特性與反射知識梳理

C#中的**特性&#xff08;Attributes&#xff09;和反射&#xff08;Reflection&#xff09;**是兩個非常重要的概念&#xff0c;它們通常用于代碼的元編程&#xff0c;允許你在運行時獲取類型信息并對其進行操作。下面對這兩個概念進行詳細梳理&#xff1a;一、C#中的特性&…

SQL 語法詳解

SQL 語法詳解 引言 SQL&#xff08;Structured Query Language&#xff09;是一種用于數據庫管理的標準語言&#xff0c;它允許用戶進行數據的查詢、更新、插入和刪除等操作。SQL語法是數據庫管理和編程的基礎&#xff0c;本篇文章將詳細介紹SQL的基本語法和常用操作&#xff0…

為什么 sim(3) 中的尺度 s 與旋轉 R 相乘,而不是平移 t?

文章目錄為什么 sim(3) 中的尺度 s 與旋轉 R 相乘&#xff0c;而不是平移 t&#xff1f;1?? sim(3) vs SE(3)&#xff1a;結構對比與核心差異2?? 為什么尺度 s 不乘在 t 上&#xff1f;&#x1f6ab; 數學破壞&#xff1a;&#x1f9ed; 幾何解釋&#xff1a;3?? t 是“相…

如何為你的 Docker 容器設置代理網絡

一文搞定!如何為你的 Docker 容器設置代理網絡(及一個最常見的“坑”) 你是否遇到過這樣的窘境:在你的服務器上,代理工具(比如 Clash, V2Ray)運行得好好的,瀏覽器也能科學上網,但一旦把應用放進 Docker 容器,它就瞬間“失聯”,無法訪問外部世界? 別擔心,這是每個…

LeetCode Day3 -- 哈希表

目錄 1. 啥是哈希表&#xff1f; 2. 啥時候用哈希表&#xff1f; 2.1 存在性檢查 → 集合Set 2.2 鍵值映射 → 字典Dict 2.3 頻率統計 → Dict or Counter 3. LeetCode 3.1 集合 &#xff08;1&#xff09;2215 找出兩數組的不同 &#xff08;2&#xff09;1207 獨一無…

三子棋裝置(電賽24E題)K230/STM32全開源

三子棋裝置&#xff08;電賽24E題&#xff09;K230/STM32全開源&#xff0c;后續有具體代碼參數講解&#xff0c;幫助大家移植k230代碼import time, os, sysfrom media.sensor import * from media.display import * from media.media import *from machine import UART from m…

終端安全檢測與防御

1. 終端安全風險主要問題&#xff1a;企業網絡中80%的安全事件源于終端&#xff0c;終端成為黑客攻擊的重要目標。攻擊手段&#xff1a;勒索病毒&#xff1a;直接勒索用戶。橫向滲透&#xff1a;通過受控終端攻擊內部服務器。僵尸網絡危害&#xff1a;信息竊取、釣魚網站引導、…

Video_AVI_Packet(2)

博主聲明&#xff1a;內容來自網絡&#xff0c;僅供參考&#xff0c;僅適用于淺了解&#xff0c;如有錯誤&#xff0c;自行甄別&#xff0c;由此引起的后果概不負責 Video_AVI_Packet&#xff08;2&#xff09;一、Video Picture Aspect Ratio 與 Active Format Aspect Ratio1.…

八月補丁星期二:微軟修復 111 個漏洞

微軟將在2025 年 8 月補丁星期二修復 111 個漏洞&#xff0c;這一數量與近期平均水平大致相同。 與上個月的情況類似&#xff0c;微軟知道今天發布的漏洞中只有一個已被公開披露&#xff0c;但聲稱沒有證據表明存在野外利用。同樣&#xff0c;截至發布時&#xff0c;唯一的補丁…

《C++進階之繼承多態》【普通類/模板類的繼承 + 父類子類的轉換 + 繼承的作用域 + 子類的默認成員函數】

【普通類/模板類的繼承 父類&子類的轉換 繼承的作用域 子類的默認構造函數】目錄前言&#xff1a;------------------------一、繼承的定義和使用1. 什么使繼承&#xff1f;2. 為什么要引入繼承&#xff1f;3. 怎么使用繼承&#xff1f;① 父類&#xff08;基類&#xf…

Ubuntu22.04安裝OBS Studio

OBS官網的最新的雖然支持Ubuntu系統&#xff0c;但是只支持最新的24.2版本的&#xff0c;而我的電腦上的Ubuntu的版本是22.04&#xff0c;所以在網上尋求解決辦法&#xff0c;看到了這一片博客&#xff0c;作為參考來實現ubuntu22.04安裝OBS&#xff0c;這里提示一下&#xff0…

Ansible 基本使用

Ansible 清單 靜態主機清單 主機清單支持多種格式&#xff0c;例如ini、yaml、腳本等。 本次課程使用 ini 格式。 #創建主機清單[lykcontroller ~ 13:36:01]# vim inventory#vim添加controllernode1node2node3node4?#測試連接單個服務器[lykcontroller ~ 14:08:18]$ ansibl…

網絡資源模板--基于Android Studio 實現的九寨溝App

目錄 一、測試環境說明 二、項目簡介 三、項目演示 四、部設計詳情&#xff08;部分) 首頁 購票頁面 五、項目源碼 一、測試環境說明 電腦環境 Windows 11 編寫語言 JAVA 開發軟件 Android Studio (2020) 開發軟件只要大于等于測試版本即可(近幾年官網直接下載也…