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倍以内。



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下可以进行子调用栈折叠。

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结果

  • 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 功能

原作者

[fastgrind] 一个轻量级C++内存监控及可视化开源库的更多相关文章

  1. 【SimpleMsgPack.NET】发布一个msgpack协议C#版本的解析开源库

    这两年一直都关注这IOCP在网络通信这方面的应用,当然数据的传递是经常需要的.今年接触了MsgPack格式,发现他用来做传输时数据打包真是太爽了.因为他可以直接打包二进制数据,不需要任何的转换.有人会 ...

  2. 一个采用python获取股票数据的开源库,相当全,及一些量化投资策略库

    tushare: http://tushare.waditu.com/index.html 为什么是Python? 就跟javascript在web领域无可撼动的地位一样,Python也已经在金融量化 ...

  3. 用python 10min手写一个简易的实时内存监控系统

    简易的内存监控系统 本文需要有一定的python和前端基础,如果没基础的,请关注我后续的基础教程系列博客 文章github源地址,还可以看到具体的代码,喜欢请在原链接右上角加个star 腾讯视频链接 ...

  4. [转]用python 10min手写一个简易的实时内存监控系统

    简易的内存监控系统 本文需要有一定的python和前端基础,如果没基础的,请关注我后续的基础教程系列博客 文章github源地址,还可以看到具体的代码,喜欢请在原链接右上角加个star 腾讯视频链接 ...

  5. 10min 手写一个内存监控系统

    本文的目的在于,尽可能用简单的代码,让大家了解内存监控的原理,及思想.更容易去理解Nagios.Zabbix.Ganglia监控原理,文章最后还有视频教程链接哦,从零敲出来的全过程 思路分为下面几块: ...

  6. iOS微信内存监控

    WeTest 导读 目前iOS主流的内存监控工具是Instruments的Allocations,但只能用于开发阶段.本文介绍如何实现离线化的内存监控工具,用于App上线后发现内存问题. FOOM(F ...

  7. 微信团队原创分享:iOS版微信的内存监控系统技术实践

    本文来自微信开发团队yangyang的技术分享. 一.前言 FOOM(Foreground Out Of Memory),是指App在前台因消耗内存过多引起系统强杀.对用户而言,表现跟crash一样. ...

  8. jvm内存监控

    jstack -- 如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程 ...

  9. Android 分区和内存监控

    Android 分区和内存监控 Andorid之所以是分区,是因为各自有对应的功能和用途的考量,可以进行单独读写和格式化. Android 设备包含两类分区: 一类是启动分区,对启动过程至关重要. 一 ...

  10. **IOS:xib文件解析(xib和storyboard的比较,一个轻量级一个重量级)

    使用Xcode做iOS项目,经常会和Xib文件打交道,因为Xib文件直观的展现出运行时视图的外观,所以上手非常容易,使用也很方便,但对于从未用纯代码写过视图的童鞋,多数对Xib的理解有些片面. Xib ...

随机推荐

  1. 使用RestCloud ETL Shell组件实现定时调度DataX离线任务

    RestCloud ETL社区版是一款数据集成工具,提供可视化多数据管道构建.数据源管理.运行监控及权限管理功能. 1.场景说明: 对于一些已经在使用阿里的离线数据同步工具DataX的用户,想实现每天 ...

  2. ICEE-Bluetooth-5.4(PwAR超级节能长期无连接双向通讯)/5.3/5.2/5.1/5.0/4.0/3.0

    https://cn.silabs.com/blog/the-new-bluetooth-5-4-what-you-should-know-first Bluetooth Core Specifica ...

  3. SciTech-Mathmatics-automatic equation Numbering \$\begin{equation} / \tag{E} / \label{E} / \\ref{E} \\end{equation}

    official docs: https://docs.mathjax.org/en/latest/input/tex/eqnumbers.html ote that the AMS environm ...

  4. ICEE-Power-开关电源:常用改输出电压的原理 及 元器件作用

    固定电压切换为可调电压电路: 输出电压调整,不仅要调整采样比例电阻, 还要解除或调整防止过压保护电路: 而保护电路,不只是输出端有,而且驱动功率管的PWM芯片也可能有: 光耦反馈电路: 输出电压(48 ...

  5. JAVA基础-4-.数据类型--九五小庞

                  练习代码:     1 public class Demo1 { 2 public static void main(String[] args) { 3 System.o ...

  6. bsfgo 一个轻量级的go gin框架,用于web站点和api开发【开源】

    bsfgo 一个轻量级的go gin框架,用于web站点和api开发. 开源地址: https://gitee.com/chejiangyi/bsfgo 介绍 bsf的go版本bsfgo,期望通过集成 ...

  7. 测试员常用抓包工具:fiddler和wireshark对比

    https://baijiahao.baidu.com/s?id=1612020651990482782 抓包就是将网络传输发送与接收的数据包进行截获.重发.编辑.转存等操作,也用来检查网络安全.抓包 ...

  8. CF893C Rumor (并查集)

    codeforces地址:https://codeforces.com/problemset/problem/893/C CF893C Rumor 题目描述 Vova promised himself ...

  9. centos6系列版本防火墙图形化设置

    1 system-config-firewall 图形化配置命令 第一步是选择信任的服务(trusted service): 常用的服务有:DNS(53)  ftp(21) imap(993) ips ...

  10. 【C++ Primer Plus】C++11 深入理解右值、右值引用和完美转发

    1. 右值引用和移动语义 1.1 左值和右值 左值 local value:存储在内存中.有明确存储地址(可寻址)的数据(x.y.z) 右值 read value:不一定可以寻址,例如存储于寄存器中的 ...