Stories

Detail Return Return

[fastgrind] 一個輕量級C++內存監控及可視化開源庫 - Stories Detail

目錄
  • Fastgrind
    • 引言
    • 簡介
    • 倉庫結構
    • 快速開始
      • 編譯 testcase
      • 運行 testcase
      • 調用堆棧 Report
    • 如何在你的項目中使用
      • 手動插樁的使用方法
      • 自動插樁的使用方法
    • fastgrind 輸出與分析
      • fastgrind.text
      • fastgrind.json
    • 可視化
    • fastgrind 編譯選項
      • 手動插樁的編譯選項
        • 編譯選項:
        • 鏈接選項:
      • 自動插樁的編譯選項
        • 編譯選項:
        • 鏈接選項:
    • 限制和注意事項
    • 原作者

Fastgrind

GitHub: https://github.com/adny-code/fastgrind

引言

在高性能計算場景下,常使用perf工具進行函數級別的時間分析、使用valgrind工具進行內存泄漏和內存分配異常檢測。

valgrind功能非常強大,能追蹤每一段內存申請和釋放的棧幀。但是valgrind使用相對複雜,最重要的是valgrind效率極其低下,通常為原始程序運行時間長的10倍以上。對於多線程程序,valgrind無法跑滿線程,性能退化能到幾十甚至上百倍。

大部分場景下,即使精簡case後,valgrind依然難以快速定位內存異常問題。

對於上述問題,fastgrind開源庫提供一個輕量級的、函數級別監控、可視化的、高效C++內存監控方案。在64核服務器上測試,64個線程能完全跑滿。簡單case (調用棧深度10以內),性能幾乎無退化;複雜case (調用棧深度30+),性能退化4倍以內。

box grouping
fastgrind倉庫的testcase中,提供了一個包含bin query、分組算法的box grouping測例,調整線程數量,測試得到的benchmark如上圖所示。

簡介

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

fastgrind 兼容C++11以上版本,集成到工程中不影響原始倉庫中其它第三方內存管理庫或glibc內存管理的正常運行。

倉庫結構

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/  # 第三方開源庫測試 (https://github.com/google/leveldb)
|   └── thirdparty_zlib_test      # 第三方開源庫測試 (https://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

程序退出時會生成兩個報告文件

[FASTGRIND] Start summary memory info
[FASTGRIND] saved: fastgrind.text (size=2335 bytes)
[FASTGRIND] saved: fastgrind.json (size=65952 bytes)

更多細節請看本文段落: fastgrind 輸出與分析

如何在你的項目中使用

手動和自動插樁都需要額外的編譯標誌

有關詳細編譯和鏈接選項,請看本文段落: fastgrind 編譯選項

手動插樁的使用方法

通過顯示的插入__FASTGRIND__::FAST_GRIND宏,選擇要監控的函數。

#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,並通過編譯選項,使得目標外的所有函數都會自動監控。

fastgrind 輸出與分析

當集成 fastgrind 的應用程序退出時,會自動生成兩個文件:fastgrind.textfastgrind.json

fastgrind.text

​fastgrind.text 是類似於 Linux 下 perf report 格式的輸出。有函數級別的調用棧內存申請/釋放統計,在vscode等editor下可以進行子調用棧摺疊。
zlib-ouput示意圖

fastgrind.json

fastgrind.json 文件中包含:

  • 時間片內存使用統計
  • 每線程內存分配詳細信息
  • 完整的調用堆棧信息
  • 函數級分配明細

每時間片、每線程、每函數記錄器:

  • 單線程記錄如下
    單線程

  • 多線程記錄如下
    多線程

可視化

使用tools/fastgrind.py生成交互式可視化折線圖

它將調用 matplotlib 繪製折線圖,​​並生成 fastgrind.html以防用户環境中沒有 matplotlib,使用瀏覽器打開fastgrind.html可以得到與matplotlib相同的折線圖

用法

python fastgrind.py fastgrind.json
or 
python fastgrind.py     # 自動搜索當前文件夾中的 fastgrind.json

以第三方開源庫 leveldb 為例,監控其內存分配:

  • matplot結果
    leveldb_plot

  • html結果
    leveldb_html

fastgrind 編譯選項

手動插樁的編譯選項

描述: 手動檢測要求開發人員在源代碼中顯式添加__FASTGRIND__::FAST_GRIND到需要監控的函數,但只需要更簡單的編譯配置

編譯選項:

g++ -O3 -Wall -Wextra -std=c++11 \
    -I/path/to/fastgrind/include \
    source_files...
    # -DFASTGRIND_JE_MALLOC (如果原工程中使用jemalloc的話需要定義該選項)
    # -DFASTGRIND_TC_MALLOC (如果原工程中使用tcmalloc的話需要定義該選項)

鏈接選項:

  • Wrap flags: 內存分配器的符號包裝(下面列出了所有支持的)

    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

自動插樁的編譯選項

描述: 自動插樁能監控所有非排除函數,但需要更復雜的編譯配置

編譯選項:

# 不需要監控的庫或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: https://github.com/adny-code/fastgrind

Add a new Comments

Some HTML is okay.