C++ 性能分析的实战指南(gperftools工具)[建议收藏]

ThatJin 2024-08-16 11:35:09 阅读 95

文章目录

使用gperftools进行 C++ 性能分析的实战指南一、编译安装 gperftools1. 下载源代码:2. 编译和安装:

二、编写测试程序三、使用 gperftools 代码示例四、查看分析结果五、一份实际代码实例及实操1.代码实例2.操作命令3.结果分析根据上述数据,对关键函数分析

六、后论

- 推荐文章 -C++音视频

使用gperftools进行 C++ 性能分析的实战指南

在软件开发过程中,性能优化是一项至关重要的任务,尤其是对于复杂的 C++ 应用程序来说。gperftools 是一套功能强大的性能分析工具,它为 C++ 开发者提供了分析 CPU 使用和内存使用的有效手段。在这篇博客中,我们将详细介绍如何在 C++ 项目中使用 gperftools 来识别和解决性能瓶颈。

一、编译安装 gperftools

首先,我们需要安装 gperftools。以下步骤将指导您完成下载和安装过程:

1. 下载源代码:

<code>wget https://github.com/gperftools/gperftools/releases/download/gperftools-2.15/gperftools-2.15.tar.gz

tar -xvf gperftools-2.15.tar.gz

2. 编译和安装:

cd gperftools-2.15

./configure --prefix=$PWD/build --enable-shared=no --enable-static=yes --enable-libunwind

make -j8

make install

注意:请确保你的 CMake 版本在 3.12 或以上。

二、编写测试程序

安装完成后,我们可以编写一个简单的 C++ 程序来测试 gperftools 的功能。以下是编译程序所需的命令:

g++ -o main hot.cpp -I./include -L ./lib -lprofiler -ltcmalloc -lpthread

这里,我们假设源文件名为 hot.cpp

三、使用 gperftools 代码示例

在 C++ 程序中,可以通过以下方式集成 gperftools:

#include <gperftools/profiler.h>

#include <gperftools/heap-profiler.h>

int main() {

ProfilerStart("cpu-profiler.prof");

HeapProfilerStart("memory-profiler");

// 你的代码

ProfilerStop();

HeapProfilerDump("test");

HeapProfilerStop();

return 0;

}

在上面的例子中,我们使用 ProfilerStartHeapProfilerStart 开启 CPU 和内存分析,执行我们需要测试的代码,然后使用 ProfilerStopHeapProfilerStop 停止分析。

四、查看分析结果

生成的分析文件可以使用 gperftools 提供的 pprof 工具来查看:

pprof ./main ./cpu-profiler.prof --text

pprof ./main ./memory-profiler.0001.heap --text

这些命令将输出文本形式的性能分析报告,帮助理解程序中的热点。

五、一份实际代码实例及实操

1.代码实例

假设应用程序包含了大量的数据库操作和数据处理,可以通过 gperftools 来识别哪些函数占用了最多的 CPU 时间或内存:

#include <iostream>

#include <vector>

#include <thread>

#include <mutex>

#include <condition_variable>

#include <queue>

#include <chrono>

#include <random>

#include <memory>

#include <functional>

// 数据库模拟类

class Database {

private:

std::mutex db_mutex;

public:

int getData(int id) {

std::lock_guard<std::mutex> lock(db_mutex);

std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 模拟数据库延迟

return id * id; // 简单计算,模拟数据处理

}

void updateData(int id, int value) {

std::lock_guard<std::mutex> lock(db_mutex);

std::this_thread::sleep_for(std::chrono::milliseconds(15)); // 模拟写入延迟

// 实际更新操作省略

}

};

// 请求类

struct Request {

int id;

int value;

bool isRead; // true if read, false if write

};

// 处理请求的工作线程

class Worker {

public:

Worker(std::shared_ptr<Database> db) : db_(db) { }

void operator()(const Request& request) {

auto start = std::chrono::high_resolution_clock::now();

if (request.isRead) {

int result = db_->getData(request.id);

std::cout << "Read request for ID " << request.id << ": " << result << std::endl;

} else {

db_->updateData(request.id, request.value);

std::cout << "Write request for ID " << request.id << " with new value " << request.value << std::endl;

}

auto end = std::chrono::high_resolution_clock::now();

std::chrono::duration<double, std::milli> elapsed = end - start;

std::cout << "Request ID " << request.id << " processed in " << elapsed.count() << " ms" << std::endl;

}

private:

std::shared_ptr<Database> db_;

};

#include <gperftools/profiler.h>

#include <gperftools/heap-profiler.h>

int main() {

ProfilerStart("cpu-profiler.prof");

HeapProfilerStart("mempry-profiler");

std::shared_ptr<Database> db = std::make_shared<Database>();

std::vector<std::thread> workers;

std::vector<Request> requests;

// 生成请求

for (int i = 0; i < 1000; ++i) {

requests.emplace_back(Request{ i, i * 100, i % 2 == 0});

}

// 创建并启动工作线程

for (auto& req : requests) {

workers.emplace_back(Worker(db), std::ref(req));

}

// 等待所有线程完成

for (auto& worker : workers) {

if (worker.joinable()) {

worker.join();

}

}

ProfilerStop();

HeapProfilerDump("test");

HeapProfilerStop();

return 0;

}

2.操作命令

[root@iZj6c0il0t6l26ze71vq4cZ build]# ls

bin hot.cpp include lib share

[root@iZj6c0il0t6l26ze71vq4cZ build]# g++ -o main hot.cpp -I./include -L ./lib -lprofiler -ltcmalloc -lpthread

[root@iZj6c0il0t6l26ze71vq4cZ build]# ls

bin hot.cpp include lib main share

[root@iZj6c0il0t6l26ze71vq4cZ build]# ./main > /dev/null

Starting tracking the heap

PROFILE: interrupts/evictions/bytes = 9/0/672

Dumping heap profile to mempry-profiler.0001.heap (test)

[root@iZj6c0il0t6l26ze71vq4cZ build]# ls

bin cpu-profiler.prof hot.cpp include lib main mempry-profiler.0001.heap share

[root@iZj6c0il0t6l26ze71vq4cZ build]# bin/pprof ./main ./cpu-profiler.prof --text

Using local file ./main.

Using local file ./cpu-profiler.prof.

Total: 9 samples

2 22.2% 22.2% 2 22.2% __GI___munmap

2 22.2% 44.4% 2 22.2% __pthread_create_2_1

1 11.1% 55.6% 1 11.1% MallocHook::InvokeDeleteHookSlow (inline)

1 11.1% 66.7% 1 11.1% SpinLock::Lock (inline)

1 11.1% 77.8% 1 11.1% __futex_abstimed_wait_common

1 11.1% 88.9% 1 11.1% std::forward

1 11.1% 100.0% 1 11.1% std::locale::id::_M_id@@GLIBCXX_3.4

0 0.0% 100.0% 1 11.1% DeleteHook

0 0.0% 100.0% 2 22.2% MallocHook::InvokeDeleteHook (inline)

0 0.0% 100.0% 2 22.2% MallocHook::InvokeDeleteHookSlow

0 0.0% 100.0% 1 11.1% RecordFree (inline)

0 0.0% 100.0% 1 11.1% SpinLockHolder::SpinLockHolder (inline)

0 0.0% 100.0% 1 11.1% Worker::operator

0 0.0% 100.0% 2 22.2% __GI___nptl_deallocate_stack

0 0.0% 100.0% 3 33.3% __clone3

0 0.0% 100.0% 3 33.3% __gnu_cxx::new_allocator::construct

0 0.0% 100.0% 6 66.7% __libc_start_call_main

0 0.0% 100.0% 6 66.7% __libc_start_main_alias_2

0 0.0% 100.0% 2 22.2% __nptl_free_stacks

0 0.0% 100.0% 3 33.3% __pthread_clockjoin_ex

0 0.0% 100.0% 6 66.7% _start

0 0.0% 100.0% 2 22.2% invoke_hooks_and_free

0 0.0% 100.0% 6 66.7% main

0 0.0% 100.0% 3 33.3% start_thread

0 0.0% 100.0% 1 11.1% std::__invoke

0 0.0% 100.0% 1 11.1% std::__invoke_impl

0 0.0% 100.0% 3 33.3% std::allocator_traits::construct

0 0.0% 100.0% 3 33.3% std::error_code::default_error_condition@@GLIBCXX_3.4.11

0 0.0% 100.0% 1 11.1% std::num_put::_M_insert_float

0 0.0% 100.0% 1 11.1% std::ostream::_M_insert

0 0.0% 100.0% 1 11.1% std::thread::_Invoker::_M_invoke

0 0.0% 100.0% 1 11.1% std::thread::_Invoker::operator

0 0.0% 100.0% 2 22.2% std::thread::_M_start_thread@@GLIBCXX_3.4.22

0 0.0% 100.0% 1 11.1% std::thread::_State_impl::_M_run

0 0.0% 100.0% 1 11.1% std::thread::_State_impl::_State_impl

0 0.0% 100.0% 2 22.2% std::thread::_State_impl::~_State_impl

0 0.0% 100.0% 3 33.3% std::thread::join@@GLIBCXX_3.4.11

0 0.0% 100.0% 3 33.3% std::thread::thread

0 0.0% 100.0% 1 11.1% std::use_facet

0 0.0% 100.0% 3 33.3% std::vector::emplace_back

[root@iZj6c0il0t6l26ze71vq4cZ build]#

3.结果分析

在这里插入图片描述

根据上述数据,对关键函数分析

<code>__GI___munmap 和 __pthread_create_2_1

这两个函数各占用了22.2%的样本,是CPU使用最多的两个函数。__GI___munmap 被用于内存映射的取消,通常与资源释放相关。而 __pthread_create_2_1 与线程创建相关,表明线程的创建和管理是CPU消耗的一个重要部分。

内存和同步相关的函数

MallocHook::InvokeDeleteHookSlowSpinLock::Lock 分别占用了11.1%的样本。这显示内存分配和线程同步也是性能消耗的关键点。

其他系统调用和库函数

__futex_abstimed_wait_commonstd::forward 也各占有11.1%,表明系统级同步和模板函数的使用对性能有较大影响。

六、后论

通过这篇博客,应该能够掌握使用 gperftools 来分析和优化 C++ 应用程序的基本方法。无论是 CPU 还是内存优化,gperftools 都是一个强大的工具,可以帮助程序提升应用性能。

gperftools 提供了主要包括 CPU 分析器和堆分析器。适合用来识别程序中的性能热点和内存泄漏。对于想要进行全面性能和资源优化的需求来说,可能还需要考虑一些其他工具和技术作为 gperftools 的互补。

以下是对 gperftools 工具的一些补充:

Valgrind

Valgrind 是一个编程工具套件,用于内存调试、内存泄漏检测以及性能分析。虽然 gperftools 的堆分析器能够帮助检测内存泄漏,Valgrind 的 Memcheck 工具在某些情况下可能提供更详细的内存访问和泄漏信息。

AddressSanitizer

AddressSanitizer (ASan) 是一个快速的内存错误检测器,可以检测出各种内存访问错误。ASan 被集成在 LLVM/Clang 和 GCC 中,与 gperftools 相比,它在运行时插入的检查可以自动发现如使用后释放、堆栈缓冲区溢出等错误。

ThreadSanitizer

ThreadSanitizer (TSan) 是用于检测数据竞争的工具。如果应用程序涉及复杂的多线程,TSan 可以作为 gperftools 的有力补充,帮助识别可能导致不稳定行为和奇怪的 bug 的数据竞争问题。

VisualVM

对于需要分析 Java 应用程序性能的开发者,VisualVM 提供了一套完整的可视化工具,包括线程分析、堆分析和 CPU 分析等。虽然这不是针对 C++ 的工具,但它展示了集成性能分析工具的方向,对于混合语言开发环境非常有用。

Intel VTune Profiler

对于需要在硬件层面上进行性能分析的开发者,Intel VTune Profiler 提供了深入的硬件级性能洞察,包括 CPU 利用率、缓存命中率、分支预测错误等。这对于优化依赖于 CPU 性能的应用程序特别有价值。

Perf

Perf 是 Linux 下的一个性能计数器工具,它可以访问 CPU 性能计数器、追踪点等,用于更底层的性能分析。Perf 能够帮助开发者分析应用程序与操作系统之间的交互,并识别潜在的性能问题。

SystemTap

SystemTap 提供了一种方法,允许管理员和开发者在 Linux 系统上运行的实时内核中编写和执行脚本,以收集关于系统的运行信息。这是研究和解决操作系统级性能问题的一个强大工具。

通过将 gperftools 与这些工具结合使用,可以获得更全面的性能分析视角,有效地优化和改进软件。

the end~


- 推荐文章 -

C++

C++11 玩家不得不学的语法集 [持续更新-建议收藏]C++ 性能分析的实战指南(gperftools工具)C++哈希算法(即散列表)的说明和代码示例

音视频

libyuv 操作 YUV420P ,如镜像、旋转、缩放等示例代码




声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。