[fastgrind] 一個(ge)輕(qing)量級C++內存監控及可視(shi)化開源庫(ku)
Fastgrind
GitHub:
引言
在高性(xing)能計算(suan)場(chang)景下(xia),常(chang)使用(yong)perf工(gong)具進(jin)行(xing)函數級(ji)別的時間分(fen)析、使用(yong)valgrind工(gong)具進(jin)行(xing)內(nei)(nei)存泄漏和內(nei)(nei)存分(fen)配異(yi)常(chang)檢測。
valgrind功能非常(chang)強大,能追蹤(zong)每(mei)一段內(nei)存(cun)申(shen)請(qing)和釋放的棧幀。但是valgrind使用相對(dui)復雜,最重(zhong)要的是valgrind效(xiao)率極其低下,通(tong)常(chang)為(wei)原(yuan)始程(cheng)(cheng)序運(yun)行(xing)時(shi)間長(chang)的10倍以上。對(dui)于多線程(cheng)(cheng)程(cheng)(cheng)序,valgrind無法跑滿(man)線程(cheng)(cheng),性能退化能到幾十甚至上百倍。
大(da)部(bu)分場景下,即(ji)使(shi)精簡case后,valgrind依(yi)然難(nan)以快速定位內存異常問題。
對于上述(shu)問題,fastgrind開源庫提供一個輕量(liang)級的、函數級別(bie)監控(kong)、可(ke)視化的、高效C++內(nei)存(cun)監控(kong)方案。在64核服(fu)務器上測試,64個線程能完全跑滿。簡(jian)單(dan)case (調用棧(zhan)深(shen)度(du)10以(yi)內(nei)),性(xing)能幾(ji)乎(hu)無退化;復(fu)雜(za)case (調用棧(zhan)深(shen)度(du)30+),性(xing)能退化4倍以(yi)內(nei)。

fastgrind倉庫的testcase中,提供了(le)一個包含bin query、分(fen)組算法的box grouping測(ce)例,調整線程數量,測(ce)試得到的benchmark如上(shang)圖所示。
簡介
fastgrind 是一個僅單一頭(tou)文件、輕量級、快速(su)、線程安全(quan)、類似 Valgrind 的(de)內存(cun)分析器,旨(zhi)在(zai)跟(gen)蹤 C++ 應用程序中的(de)運行(xing)時內存(cun)分配并分析調用堆(dui)棧。Fastgrind 通過自動(dong)和手動(dong)插樁兩種檢測(ce)方法提供(gong)全(quan)面的(de)內存(cun)使用情況分析。
fastgrind 兼容(rong)C++11以上版本,集成到工程中不(bu)影響原始倉庫中其它第三方內存(cun)管理庫或(huo)glibc內存(cun)管理的正常(chang)運行。
倉庫結構
fastgrind/
├── include/fastgrind.h # 核心代碼 (head only)
|
├── demo/
│ ├── manual_instrument/ # 手動插樁 demos
│ ├── auto_instrument/ # 自動插樁 demos
| └── build_all_demo.sh # 編譯所有demo的腳本
|
├── testcase/
│ ├── benchmark_box_grouping/ # 性能測試
│ ├── cpp_feature_test/ # 現代C++特性測試
│ ├── glibc_je_tc_availabe/ # 分配器兼容性測試
│ ├── multi_pkg_compile/ # 多lib編譯測試
| ├── thirdparty_leveldb_test/ # 第三方開源庫測試 (//github.com/google/leveldb)
| └── thirdparty_zlib_test # 第三方開源庫測試 (//zlib.net)
|
├── doc/
| ├── compile.md # 集成和編譯選項說明
| ├── demo.md # demo說明
| ├── feature_list.md # fastgrind特性說明
| ├── querstion_list.md # fastgrind使用過??程中出現的問題及解決方案說明
| └── testcase.md # testcase說明
|
├── tools/fastgrind.py # 可視化工具 (使用方法:python fastgrind.py fastgrind.json)
|
├── CMakeList.txt # testcase的頂層Cmake
├── Doxyfile # Doxyfile生成手冊
└── README.md # 存庫描述
快速開始
編譯 testcase
mkdir build && cd build
cmake ..
make -j$(nproc)
運行 testcase
cd build/testcase/benchmark_box_grouping
./benchmark_raw
./benchmark_fastgrind
./run_valgrind.sh
cd build/testcase/cpp_feature_test
./cpp_feature_test
...
cd build/testcase/multi_pkg_compile
./multi_pkg_main
調用堆棧 Report
程序退(tui)出時會生成兩個報告(gao)文件
[FASTGRIND] Start summary memory info
[FASTGRIND] saved: fastgrind.text (size=2335 bytes)
[FASTGRIND] saved: fastgrind.json (size=65952 bytes)
更多細節請看本文段落: fastgrind 輸出與分析
如何在你的項目中使用
手動和自動插樁都需要額外的編譯標志
有關詳細編譯和鏈接選項,請看本文段落: fastgrind 編譯選項
手動插樁的使用方法
通(tong)過顯示(shi)的插入__FASTGRIND__::FAST_GRIND宏,選擇(ze)要監控的函數。
#include "fastgrind.h"
using namespace __FASTGRIND__;
void processData() {
FAST_GRIND; // 啟用此函數的調用堆棧跟蹤
int* data = new int[1000];
// ... process data ...
delete[] data;
}
int main() {
FAST_GRIND; // 啟用此函數的調用堆棧跟蹤
processData();
return 0;
}
自動插樁的使用方法
在任何一個.cpp中包含 fastgrind.h,并通(tong)過(guo)編譯選項,使得目(mu)標外的所有(you)函數(shu)都會自動監控。
fastgrind 輸出與分析
當集成 fastgrind 的應用程序退出時,會自動生成兩個文件:fastgrind.text和fastgrind.json
fastgrind.text
?fastgrind.text 是類似于 Linux 下 perf report 格式的輸出。有函數級別的調用棧內存申請/釋放統計,在vscode等editor下可以進行子調用棧折疊。

fastgrind.json
fastgrind.json 文件中包含:
- 時間片內存使用統計
- 每線程內存分配詳細信息
- 完整的調用堆棧信息
- 函數級分配明細
每時間片、每線程、每函數記錄器:
-
單線程記錄如下
![單線程]()
-
多線程記錄如下
![多線程]()
可視化
使用tools/fastgrind.py生成交(jiao)互式可視化折線圖
它將調用 matplotlib 繪制折線圖,??并生成 fastgrind.html以防用戶環境中沒有 matplotlib,使用瀏覽器打開fastgrind.html可以得到與(yu)matplotlib相同的(de)折線圖(tu)
用法
python fastgrind.py fastgrind.json
or
python fastgrind.py # 自動搜索當前文件夾中的 fastgrind.json
以第三方開源庫 leveldb 為例,監控其內存分配:
-
matplot結果
![leveldb_plot]()
-
html結果
![leveldb_html]()
fastgrind 編譯選項
手動插樁的編譯選項
描述: 手動檢測要求開發人員在源代碼中顯式添加__FASTGRIND__::FAST_GRIND到(dao)需要監控(kong)的(de)(de)函數,但只需要更簡單的(de)(de)編譯(yi)配置
編譯選項:
g++ -O3 -Wall -Wextra -std=c++11 \
-I/path/to/fastgrind/include \
source_files...
# -DFASTGRIND_JE_MALLOC (如果原工程中使用jemalloc的話需要定義該選項)
# -DFASTGRIND_TC_MALLOC (如果原工程中使用tcmalloc的話需要定義該選項)
鏈接選項:
-
Wrap flags: 內存分(fen)配器的(de)(de)符號包(bao)裝(下面列出了所有支持的(de)(de))
WRAP_FLAGS=( # C standard library memory allocation functions -Wl,--wrap=malloc # Standard memory allocation -Wl,--wrap=calloc # Zero-initialized memory allocation -Wl,--wrap=realloc # Memory reallocation -Wl,--wrap=free # Memory deallocation # C++ standard operator new/delete (basic versions) -Wl,--wrap=_Znwm # operator new(size_t) -Wl,--wrap=_Znam # operator new[](size_t) -Wl,--wrap=_ZdlPv # operator delete(void*) -Wl,--wrap=_ZdaPv # operator delete[](void*) # C++ nothrow operator new/delete -Wl,--wrap=_ZnwmRKSt9nothrow_t # operator new(size_t, nothrow) -Wl,--wrap=_ZnamRKSt9nothrow_t # operator new[](size_t, nothrow) -Wl,--wrap=_ZdlPvRKSt9nothrow_t # operator delete(void*, nothrow) -Wl,--wrap=_ZdaPvRKSt9nothrow_t # operator delete[](void*, nothrow) # POSIX and Linux-specific memory allocation functions -Wl,--wrap=valloc # Page-aligned memory allocation -Wl,--wrap=pvalloc # Page-aligned allocation (multiple of page size) -Wl,--wrap=memalign # Aligned memory allocation -Wl,--wrap=posix_memalign # POSIX aligned memory allocation -Wl,--wrap=reallocarray # Array reallocation with overflow check -Wl,--wrap=aligned_alloc # C11 aligned allocation # C++ sized delete operators (C++14) -Wl,--wrap=_ZdaPvm # operator delete[](void*, size_t) -Wl,--wrap=_ZdlPvm # operator delete(void*, size_t) # C++ aligned allocation operators (C++17) -Wl,--wrap=_ZnwmSt11align_val_t # operator new(size_t, align_val_t) -Wl,--wrap=_ZnamSt11align_val_t # operator new[](size_t, align_val_t) -Wl,--wrap=_ZdlPvSt11align_val_t # operator delete(void*, align_val_t) -Wl,--wrap=_ZdaPvSt11align_val_t # operator delete[](void*, align_val_t) # C++ sized aligned delete operators (C++17) -Wl,--wrap=_ZdlPvmSt11align_val_t # operator delete(void*, size_t, align_val_t) -Wl,--wrap=_ZdaPvmSt11align_val_t # operator delete[](void*, size_t, align_val_t) # C++ nothrow sized delete operators -Wl,--wrap=_ZdlPvmRKSt9nothrow_t # operator delete(void*, size_t, nothrow) -Wl,--wrap=_ZdaPvmRKSt9nothrow_t # operator delete[](void*, size_t, nothrow) # C++ nothrow aligned allocation operators (C++17) -Wl,--wrap=_ZnwmSt11align_val_tRKSt9nothrow_t # operator new(size_t, align_val_t, nothrow) -Wl,--wrap=_ZnamSt11align_val_tRKSt9nothrow_t # operator new[](size_t, align_val_t, nothrow) -Wl,--wrap=_ZdlPvSt11align_val_tRKSt9nothrow_t # operator delete(void*, align_val_t, nothrow) -Wl,--wrap=_ZdaPvSt11align_val_tRKSt9nothrow_t # operator delete[](void*, align_val_t, nothrow) # C++ nothrow sized aligned delete operators -Wl,--wrap=_ZdlPvmSt11align_val_tRKSt9nothrow_t # operator delete(void*, size_t, align_val_t, nothrow) -Wl,--wrap=_ZdaPvmSt11align_val_tRKSt9nothrow_t # operator delete[](void*, size_t, align_val_t, nothrow) )
具體示例可看原倉庫中demo/manual_instrument
自動插樁的編譯選項
描述: 自動插樁能監控所有非排除函數,但需要更復雜的(de)編譯(yi)配置
編譯選項:
# 不需要監控的庫或pkg
EXCLUDE_FILE_LISTS=(
/usr/include/
/usr/lib/
/usr/local/
fastgrind.h
)
EXCLUDE_FILE_LISTS=$(IFS=,; echo "${EXCLUDE_FILE_LISTS[*]}")
# 函數插樁的編譯選項
INSTRUMENT_FLAGS=(
-finstrument-functions
-finstrument-functions-exclude-file-list=${EXCLUDE_FILE_LISTS}
)
# "${INSTRUMENT_FLAGS[@]}": 不要監控的庫或pkg
# -DFASTGRIND_INSTRUMENT: 定義 FASTGRIND_INSTRUMENT 以啟動自動插樁
# -Wl,--export-dynamic: 導出符號表,便于fastgrind抓取函數名
g++ -O3 -Wall -Wextra -std=c++11 \
"${INSTRUMENT_FLAGS[@]}" \
-DFASTGRIND_INSTRUMENT \
-Wl,--export-dynamic \
-I/path/to/fastgrind/include \
source_files...
# -DFASTGRIND_JE_MALLOC (如果原工程中使用jemalloc的話需要定義該選項)
# -DFASTGRIND_TC_MALLOC (如果原工程中使用tcmalloc的話需要定義該選項)
鏈接選項:
同 手動插樁 一致
具體示例可看原倉庫中demo/auto_instrument
限制和注意事項
- 跨函數棧幀分配: 在一個函數中分配并在另一個函數中釋放的內存將被如實記錄,導致這些函數堆棧幀記錄釋放的數量少于或超過分配的數量,在另一些則相反.
- 模板復雜性支持: 復雜的模板元編程可能會在報告中顯示通用名稱
- 文件覆蓋: 每次運行時輸出文件都會覆蓋以前的內容
- GUN依賴: 需要 GNU ld 來實現
--wrap功能
原作者
- Email: zfzmalloc@gmail.com
- GitHub:

fastgrind 是一個僅單一頭文件、輕量級、快速、線程安全、類似 Valgrind 的內存分析器,旨在跟蹤 C++ 應用程序中的運行時內存分配并分析調用堆棧。fastgrind 通過自動和手動插樁兩種檢測方法提供全面的內存使用情況分析。



