掌握性能分析工具Perf(二)

文摘   2024-10-22 18:08   广东  

关注+星号公众号,不容错过精彩

作者:HywelStar

根据上一章节了解perf 工具的定义和用法,本章节将以实例进行去使用它,帮助在开发过程中去优化程序代码,让系统能够达到一个最优。

上一章:掌握性能分析工具Perf(一)

1. perf stat 整体性能分析

使用 perf stat 来获取程序的整体性能统计信息:

perf stat ./hywelstar_perf_demo

从这些统计信息中,我们可以得出以下结论:

  • 程序的总运行时间为 13 秒。
  • CPU 利用率非常高(1.010 CPUs utilized)。
  • 程序产生了 9831 次页面错误(page-faults),这可能是由于内存分配导致的。
  • 每个周期执行了 0.85 条指令,说明存在一定的性能瓶颈。

2. perf record 详细分析

为了进一步定位性能瓶颈,我们使用 perf record 记录程序运行期间的详细性能数据:

perf record -g ./hywelstar_perf_demo

然后使用 perf report 分析记录的数据:

perf report


通过这个reportperf report 中,我们可以看到各个函数的运行时间分布,以及各个函数的调用关系。通过这个工具,我们可以找出哪些函数占用了大部分的CPU时间。cpu_intensive_task, 原函数确实是在做一些耗时工作。这样就可以进行去优化。

3. 使用 perf annotate 查看热点代码

对于占用大量CPU时间的函数,可以使用 perf annotate 查看这些函数的具体代码执行情况。

perf annotate

从图中将展现热点代码,左边是运行时间的比例,右边的对照的代码,可以通过方向上下移动,查看函数之间的关系。

4. 使用perf top 进行监控

开发过程中可以通过perf top进行实时的性能监控。

perf top -p <pid>

image-20241021171218306

5. 总结

从上面这个例子查看,cpu_intensive_task 函数中,绝大部分时间消耗在以下几条指令上:

  • cvtsi2sdq 指令:将整数转换为双精度浮点数。
  • comisd 指令:比较两个双精度浮点数。

这种类型的任务是计算密集型的,优化方法包括:

  • 减少不必要的浮点数转换:检查算法是否可以优化,减少或避免浮点数的使用。
  • 并行化计算:如果可能,可以使用多线程或SIMD指令集来并行处理这些计算任务。

在实际开发过程中,优化也需要去查看自身的代码涉及性能相关的问题,一般来说通过perf 工具可以轻松找到相关问题,另外还可以通过生成火焰图进行更加直观找到问题的位置。

本案例源码 hywelstar_perf_demo.c

#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <time.h>
// 示例函数,模拟计算密集型任务void cpu_intensive_task() { for (long i = 0; i < 1e8; i++);}
// 示例函数,模拟缓存未命中任务void cache_miss_task() { int *array = (int*)malloc(1e7 * sizeof(int)); for (long i = 0; i < 1e7; i++) { array[i] = i; } free(array);}
// 示例函数,模拟上下文切换任务void* context_switch_task(void *arg) { cpu_intensive_task(); return NULL;}
// 示例函数,模拟热点函数void hotspot_function() { for (int i = 0; i < 100; i++) { cpu_intensive_task(); }}
// 示例函数,模拟系统调用void system_call_task() { for (int i = 0; i < 1000; i++) { FILE *fp = fopen("/dev/null", "w"); fprintf(fp, "test\n"); fclose(fp); }}
// 示例函数,模拟锁竞争pthread_mutex_t lock;void* lock_task(void *arg) { pthread_mutex_lock(&lock); cpu_intensive_task(); pthread_mutex_unlock(&lock); return NULL;}
int main() { // CPU 密集型任务 printf("Running CPU intensive task...\n"); cpu_intensive_task();
// 缓存未命中任务 printf("Running cache miss task...\n"); cache_miss_task();
// 上下文切换任务 printf("Running context switch task...\n"); pthread_t thread1, thread2; pthread_create(&thread1, NULL, context_switch_task, NULL); pthread_create(&thread2, NULL, context_switch_task, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL);
// 热点函数 printf("Running hotspot function...\n"); hotspot_function();
// 系统调用任务 printf("Running system call task...\n"); system_call_task();
// 锁竞争任务 printf("Running lock contention task...\n"); pthread_mutex_init(&lock, NULL); pthread_create(&thread1, NULL, lock_task, NULL); pthread_create(&thread2, NULL, lock_task, NULL);
pthread_join(thread1, NULL); pthread_join(thread2, NULL); pthread_mutex_destroy(&lock);
return 0;}



往期推荐



掌握性能分析工具Perf(一)

掌握Dynamic debug调试

掌握GDB:在Linux 下调试程序

Linux 内核镜像文件种类认识

GNSS篇(五) - GPSD 编译(含交叉编译)详解

GNSS篇(四) - 整活定位模块(GPSD 使用)

GNSS 篇(三) - 定位模块与协议介绍

GNSS 篇(二) - 卫星导航定位原理

GNSS篇(一) - 定位基础知识

USB PD 协议介绍与交互过程

Type-C 的工作原理

公众号目录指引

 

           
“阅读原文”一起来充电吧!

码思途远
一位码农的日常分享,探索软件技术知识与新闻的数字十字路口。
 最新文章