0、序
在面试过程中,经常会被提及的一个问题就是,性能优化的方法和工具。对于这个问题,笔者一开始的理解是比较狭隘的,以为只有perf之类的性能分析工具才是答案,然而这类工具使用的确不多,因此每每回答这种问题,都会老实巴交的说不太了解。
直到现在才醒悟过来,编码中的一些看似顺其自然的选择其实就是性能优化的一种方法,比如数据结构的合理选择,比如内联函数的使用...
现学习总结如下。
1、方法
1) 选择合适的算法和数据结构
-
算法复杂度分析:在解决问题之前,对不同的算法进行时间复杂度和空间复杂度分析,选择复杂度最低的算法。如在排序时,对于大规模数据,快速排序通常比冒泡排序效率高得多。
-
数据结构优化:根据实际应用场景选择合适的数据结构。例如,在频繁进行插入和删除操作的场景中,使用链表可能比数组更高效;而在需要快速随机访问元素的情况下,数组则更合适。
2) 优化代码实现
-
减少不必要的计算:避免在循环中进行重复的计算,可将循环不变式的计算提到循环外面。
-
优化循环结构:尽量减少循环的嵌套层数,因为多层嵌套循环会使时间复杂度呈指数增长。同时,在循环中要避免使用复杂的函数调用,可将函数调用的结果保存起来,避免多次调用。
-
合理使用内联函数:对于短小且频繁调用的函数,可使用内联函数,以减少函数调用的开销。但内联函数不宜过长,否则会导致代码膨胀。
3) 内存管理优化
-
减少内存分配和释放次数:频繁的内存分配和释放会导致内存碎片和性能下降。可以通过一次性分配较大的内存块,然后在需要时重复使用,或者使用内存池技术来管理内存。
-
优化内存访问模式:在访问多维数组时,按照内存连续的方式进行访问,可以提高缓存命中率,从而提高性能。例如,对于二维数组
int a[100][100]
,按a[i][j]
的顺序访问比按a[j][i]
的顺序访问更高效,因为前者在内存中是连续存储的。
4) 多线程和并行编程
-
合理划分任务:将任务划分为多个子任务,分配到不同的线程或进程中并行执行,以充分利用多核处理器的性能。但要注意避免线程之间的竞争和同步问题,可使用互斥锁、信号量等同步机制来保证数据的一致性。
-
使用线程池:避免频繁地创建和销毁线程,可使用线程池来管理线程。线程池在初始化时创建一定数量的线程,当有任务到来时,从线程池中获取空闲线程来执行任务,任务执行完毕后线程并不销毁,而是回到线程池中等待下一次任务。
2、工具
1) 编译器优化选项
-
-O 系列选项:GCC 编译器提供了不同级别的优化选项,如
-O1
、-O2
、-O3
等。-O1
会进行一些基本的优化,如减少代码大小、优化循环等;-O2
会在-O1
的基础上进行更多的优化,包括函数内联、指令重排等;-O3
则会进行更激进的优化,如循环展开、公共子表达式消除等,但可能会增加编译时间和代码大小。 -
-finline-functions:该选项会将所有简单的函数进行内联,而不仅仅是在函数声明前添加
inline
关键字的函数,可进一步减少函数调用的开销。
2) 性能分析工具(系统级)
-
perf: Linux 内核自带的性能分析工具,功能强大。它可以用于分析 CPU 性能、内存访问、进程调度等方面的问题。例如,
perf stat <可执行程序>
:可以统计程序运行过程中的各种性能指标,如 CPU 时钟周期、指令数、缓存命中率等。perf record <可执行程序>
:记录程序运行时的性能事件,运行结束后会生成一个数据文件,再通过perf report
命令对该文件进行分析,生成详细的分析报告,展示各个函数的性能开销占比等。 -
top:实时显示系统中各个进程的资源占用情况,包括 CPU 使用率、内存使用率、进程状态等,可快速定位系统中占用资源较多的进程。在命令行中输入
top
后,会列出当前系统中所有进程的相关信息,按Shift+P
可按照 CPU 使用率进行排序,按Shift+M
可按照内存使用率排序,方便查看资源消耗大户。 -
vmstat:报告关于进程、内存、I/O 和 CPU 活动的系统级统计信息,常用于监控系统的整体性能状态和资源使用趋势。在命令行输入
vmstat <时间间隔> <次数>
,如vmstat 5 10
,表示每隔 5 秒输出一次系统性能统计信息,共输出 10 次,通过这些数据可以观察到系统在一段时间内的性能变化情况。
3) 性能分析工具(应用级)
-
gprof:是 GNU 提供的性能分析工具,通过在编译时添加
-pg
选项,在程序运行结束后,会生成一个性能分析报告,显示每个函数的调用次数、执行时间等信息,帮助开发者找到程序中的性能瓶颈。 -
Callgrind:是 Valgrind 工具集的一部分,用于收集程序的调用图和函数的执行时间等信息,能够更详细地分析函数之间的调用关系和性能开销。使用
valgrind --tool=callgrind <可执行程序>
命令运行程序,运行结束后会生成一个名为callgrind.out.<pid>
的文件,其中<pid>
是程序的进程 ID。然后可以使用kcachegrind
或qcachegrind
等图形化工具来查看分析结果,直观地展示函数调用图和性能数据。 -
Intel VTune Amplifier:一款功能强大的性能分析工具,支持对 C/C++ 程序进行全面的性能分析,包括热点分析、线程分析、内存分析等,并且提供了图形化的界面,方便用户查看和分析结果。安装并打开Intel VTune Amplifier 后,创建一个新的项目,选择要分析的可执行程序,然后设置分析类型和相关参数,点击运行即可开始分析。分析结束后,在图形化界面中可以查看详细的性能数据和分析报告,如函数的执行时间、调用次数、CPU 利用率等。
4) 内存检测工具
-
valgrind:是一款用于内存调试、内存泄漏检测和性能分析的工具。它可以检测出程序中存在的内存泄漏、越界访问、非法内存访问等问题。使用
valgrind --tool=memcheck
命令可以对程序进行内存检查,它会详细地报告出程序中存在的内存问题以及问题所在的位置。 -
AddressSanitizer:是一种快速的内存错误检测工具,集成在 GCC 和 Clang 编译器中。通过在编译时添加
-fsanitize=address
选项,它可以在程序运行时检测出内存越界、使用未初始化的内存等问题,并给出详细的错误信息和调用栈。