1. 什么是 CPI ?
本小节讲述为什么使用 CPI 分析程序性能的意义。如果已经非常了解 CPI 对分析程序性能的意义,可以跳过本小节的阅读。
1.1 程序怎么样才能跑得快 ?
理解什么是 CPI,首先让我们思考一个问题:在一个给定的处理器上,如何才能让程序跑得更快呢?
假设程序跑得快慢的标准是程序的执行时间,那么程序执行的快慢,就可以用如下公式来表示:
程序执行时间 = 程序总指令数 x 每 CPU 时钟周期时间 x 每指令执行所需平均时钟周期数
因此,要想程序跑得快,即减少程序执行时间,我们就需要在以下三个方面下功夫:
- 减少每 CPU 时钟周期时间
如何减少每指令执行所需平均 CPU 时钟周期数呢?让我们先从 CPU 设计角度看一下:
- 标量处理器 (Scalar Processor) ;一个 CPU 时钟周期只能执行一条指令;
- 超标量处理器 (Superscalar Processor);一个 CPU 时钟周期可以执行多条指令;通常这个是靠在处理器里实现多级流水线 (Pipeline) 来实现的。
因此不难看出,如果使用支持超标量处理器的 CPU,利用 CPU 流水线提高指令并行度,那么就可以达到我们的目的了。流水线的并行度越高,执行效率越高,那么每指令执行所需平均时钟周期数就会越低。
由于提高 CPU 主频的同时,又要保障一个 CPU 时钟周期可以执行更多的指令,因此需要处理器厂商需要不断地提高制造工艺,降低 CPU 的芯片面积和功耗。
1.2 CPI 和 IPC
CPI = 1 / IPC
使用 CPI 这个定义,本文开篇用于衡量程序执行性能的公式实际上可以表示为:
Execution Time (T) = Instruction Count x Time Per Cycle X CPI
由于受到硅材料和制造工艺的限制,处理器主频的提高已经面临瓶颈,因此,程序性能的提高,主要的变量在 Instruction Count 和 CPI 这两个方面。
$sudo perf stat -p `pidof java`
^C
Performance counter stats for process id '3191':
1.616171 task-clock (msec) # 0.000 CPUs utilized
221 context-switches # 0.137 M/sec
0 cpu-migrations # 0.000 K/sec
2 page-faults # 0.001 M/sec
2,907,189 cycles # 1.799 GHz
2,083,821 stalled-cycles-frontend # 71.68% frontend cycles idle
1,714,355 stalled-cycles-backend # 58.97% backend cycles idle
1,561,667 instructions # 0.54 insns per cycle
# 1.33 stalled cycles per insn
286,102 branches # 177.025 M/sec
<not counted> branch-misses (0.00%)
8.841569895 seconds time elapsed那么,通过 IPC,我们也可以换算出 CPI 是 1/0.54,约为 1.85.
通常情况下,通过 CPI 的取值,我们可以大致判断一个计算密集型任务,到底是 CPU bound 还是 Memory Bound:
1.3 重新认识 CPU 利用率
1.4 如何分析 CPI/IPC 指标异常?
虽然利用 perf 可以很方便获取 CPI/IPC 指标,但是想分析和优化程序高 CPI 的问题,就需要一些工具和分析方法,将 CPI 高的原因,以及与之关联的软件的调用栈找到,从而决定优化方向。
我们稍后会在另一篇文章介绍这种分析方法,本文主要关注使用 CPI 火焰图来分析 CPI 的问题。
2. CPI 火焰图
Brendan Gregg 在 CPI Flame Graphs: Catching Your CPUs Napping 一文中,介绍了使用 CPI 火焰图来建立 CPI 和软件调用栈的关联。
而 CPI 火焰图,可以基于 CPU 火焰图,提供一个可视化的基于 CPU 利用率和 CPI 指标,综合分析程序 CPU 执行效率的方案。
下面这个 CPI 火焰图引用自 Brendan Gregg 博客文章。可以看到,CPI 火焰图是基于 CPU 火焰图,根据 CPI 的大小,在每个条加上了颜色。红色代表指令,蓝色代表流水线的停顿:
火焰图里,颜色范围,从最高CPI为蓝色(执行最慢的指令),到最低CPI为红色 (执行最快的指令)。火焰图是 SVG 格式,矢量图,因此支持鼠标点击缩放。
然而,Brendan Gregg 博客中的这篇博客,CPI 火焰图是基于 FreeBSD 操作系统特有的命令生成的,而在 Linux 上,应该怎么办呢?
3. 一个小程序
让我们写一个人造的小程序,展示在 Linux 下 CPI 火焰图的使用。
函数主体是 nop 指令的循环;由于 nop 指令是不访问内存的最简指令之一, 因此该函数 CPI 一定小于 1,属于典型的 CPU Bound 类型的代码。函数使用 `_mm_clflush` 驱逐缓存,人为触发程序的 L1 D-Cache Load Miss。 因此该函数 CPI 必然大于 1,属于典型的 Memory Bcound 的代码。
#include <stdlib.h>
#include <emmintrin.h>
#include <stdio.h>
#include <signal.h>
char a = 1;
void memory_bound() {
register unsigned i=0;
register char b;
for (i=0;i<(1u<<24);i++) {
// evict cacheline containing a
_mm_clflush(&a);
b = a;
}
}
void cpu_bound() {
register unsigned i=0;
for (i=0;i<(1u<<31);i++) {
__asm__ ("nop\nnop\nnop");
}
}
int main() {
memory_bound();
cpu_bound();
return 0;
}$ perf record -e cpu/event=0xa2,umask=0x1,name=resource_stalls_any,period=2000003/ -e cpu/event=0x3c,umask=0x0,name=cpu_clk_unhalted_thread_p,period=2000003/ --call-graph dwarf -F 200 ./cpu_and_mem_bound
$ perf script > out.perf
$ FlameGraph/stackcollapse-perf.pl --event-filter=cpu_clk_unhalted_thread_p out.perf > out.folded.cycles
$ FlameGraph/stackcollapse-perf.pl --event-filter=resource_stalls_any out.perf > out.folded.stalls
$ FlameGraph/difffolded.pl -n out.folded.stalls out.folded.cycles | FlameGraph/flamegraph.pl --title "CPI Flame Graph: blue=stalls, red=instructions" --width=900 > cpi_flamegraph_small.svg- 该程序所有的 CPU 时间,都分布在
cpu_bound和memory_bound两个函数里 - 同是 CPU 占用时间,但
cpu_bound是红色的,代表这个函数的指令在 CPU 上一直持续运行 - 而
memory_bound是蓝色的,代表这个函数发生了严重的访问内存的延迟,导致了流水线停顿,属于忙等
4. 一个benchmark
现在,我们可以使用 CPI 火焰图来分析一个略真实一些的测试场景。下面的 CPI 火焰图,来自 fio 的测试场景。
这个 fio 对 SATA 磁盘,做多进程同步 Direct IO 顺序写,可以看到:
- 红颜色为标记为 CPU Bound 的函数。其中颜色最深的是
_raw_spin_lock,这是自旋锁的等待循环引起的。 - 蓝颜色为标记为 Memory Bound 的函数。其中颜色最深的是
fio测试程序的函数get_io_u,如果使用perf程序进一步分析,这个函数里发生了严重的 LLC Cache Miss。
因为 CPI 火焰图是矢量图,支持缩放,所以以上结论可以通过放大 get_io_u 的调用栈进一步确认,
5. 小结
系统管理员可以通过此工具发现系统存在的资源瓶颈,并且通过一些系统管理命令来缓解资源的瓶颈;例如,应用间的 Cache 颠簸干扰,可以通过将应用绑到不同的 CPU 上解决。
而应用开发者则可以通过优化相关函数,来提高程序的性能。例如,通过优化代码减少 Cache Miss,从而降低应用的 CPI 来减少处理器因访存停顿造成的性能问题。
由于本文主要是介绍 CPI 火焰图,对于 1.4 小节提到的自顶向下的分析方法,限于篇幅所限,这里不详细展开了。关于此内容,我们稍后会有专门的文章做详细介绍。