tommwq的博客

off-CPU分析

· [tommwq@126.com]

来源

性能问题可以被分为两种类型:

  • on-CPU。线程时间消耗在运行于CPU上。
  • off-CPU。线程时间消耗在等待阻塞I/O、锁、时钟、分页交换等。

off-CPU分析是一种性能分析方法,这种方法度量和研究线程不在CPU上运行时的状态,而非堆栈等上下文。这与CPU分析只检查正在运行中的线程不同。off-CPU分析的目标是阻塞和非执行线程的状态,即图中右侧的蓝色状态。

off-CPU分析是对CPU分析的补充,结合起来可以理解100%的线程时间。与其他跟踪应用程序函数阻塞的技术不同,off-CPU方法关注内核调度器的阻塞概念,因此可以方便的捕获一切情况。

有多种原因可以让线程离开CPU,包括I/O、锁、以及一些和当前线程无关的因素,比如对CPU资源的需求和中断引发的程序无法感知的上下文切换。无论哪种原因,如果在工作请求(同步代码路径)中发生,都会产生延迟。

在本文中我将介绍off-CPU时间的度量,并总结off-CPU分析技术。作为实践off-CPU分析的例子,我将在Linux上应用off-CPU分析,并在后续章节中总结其他操作系统上的方法。

前提条件

off-CPU分析需要栈可以被跟踪器捕获,这可能是你首要处理的事情。很对程序使用GCC选项 -fomit-frame-pointer 进行编译,这破坏了基于指针的栈遍历。像Java一类的虚拟机运行环境在运行时编译方法,如果没有额外的帮助,跟踪器无法找到符号信息,使得栈记录只有16进制地址。此外还存在其他问题,可以参考我关于为perf修复栈记录和即时编译符号的文章。

简介

为了解释off-CPU分析的角色,我首先简单介绍和比较CPU采样(sampling)和跟踪(tracing),接着总结这两种off-CPU分析方法。虽然我推广off-CPU分析方法的时间超过10年,它的应用仍然并不广泛。部分原因是在生产环境Linux下缺少工具来衡量。随着eBPF特性和新版本Linux内核(4.8以上)的到来,这一点正在发生改变。

CPU采样

很多分析器使用CPU活动的定时采样,按照固定的时间间隔或频率,收集当前指令地址(程序计数器)或栈的快照。这种方法可以给出运行函数或栈记录的数量,以便对CPU周期的用处进行道德合理评估和计算。在Linux上, perf 工具的采样模式(比如 -F 99 )执行定时CPU采样。

考虑应用函数 A() 调用函数 B() 的场景,后者产生了一个阻塞系统调用。

 CPU Sampling ----------------------------------------------->
  |  |  |  |  |  |  |                      |  |  |  |  |
  A  A  A  A  B  B  B                      B  A  A  A  A
 A(---------.                                .----------)
            |                                |
            B(--------.                   .--)
                      |                   |         user-land
- - - - - - - - - - syscall - - - - - - - - - - - - - - - - -
                      |                   |         kernel
                      X     Off-CPU       |
                    block . . . . . interrupt

这种方法非常适合研究on-CPU问题,包括热点代码路径和自适应自旋锁。但它并不收集应用阻塞和等待CPU的信息。

应用跟踪

App Tracing ------------------------------------------------>
 |          |                                |          |
 A(         B(                               B)         A)

 A(---------.                                .----------)
            |                                |
            B(--------.                   .--)
                      |                   |         user-land
- - - - - - - - - - syscall - - - - - - - - - - - - - - - - -
                      |                   |         kernel
                      X     Off-CPU       |
                    block . . . . . interrupt

这种方法让函数可以感知阻塞,收集函数以“(”开始和以“)”结束的时间戳,以计算函数消耗的时间。如果时间戳包含实际时间(elapsed time)和CPU时间(比如使用times(2)或getrusage(2)),那么就可以计算出哪些函数是因为消耗CPU而变慢,哪些函数由于阻塞而变慢。与采样方式不同,这些时间戳可以具有很高的精度(纳秒)。

使用这种方法的缺点在于,要么你跟踪所有的应用函数,这将导致巨大的性能消耗(并影响系统性能);要么你选择几个可能阻塞的函数,然后祈祷自己没有遗漏。

off-CPU跟踪

本节是对off-CPU分析的总结,后面的章节会详细介绍这种方法。

 Off-CPU Tracing -------------------------------------------->
                      |                   |
                      B                   B
                      A                   A
 A(---------.                                .----------)
            |                                |
            B(--------.                   .--)
                      |                   |         user-land
- - - - - - - - - - syscall - - - - - - - - - - - - - - - - -
                      |                   |         kernel
                      X     Off-CPU       |
                    block . . . . . interrupt

这种方法只跟踪导致线程在on-CPU和off-CPU之间切换的内核函数,并记录时间戳和用户层栈记录。这种方法只关注与off-CPU事件,不需要记录所有的应用函数,也不需要知道应用是什么。这种方法适用于任何阻塞事件、任何应用:MySQL、Apache、Java等。

off-CPU跟踪捕获所有应用程序的全部等待事件。

后面我将跟踪内核off-CPU事件,并使用一些应用层工具来过滤异步等待时间(比如线程等待任务)。同应用层工具不同,我不需要搜寻每个可能产生off-CPU阻塞的地方,我只需要确认应用在时间敏感的代码路径上(比如在MySQL查询期间),并且延迟是同步的。

off-CPU跟踪是我进行off-CPU分析使用的主要方法,不过采样也是可行的。

off-CPU采样

 Off-CPU Sampling ------------------------------------------->
                       |  |  |  |  |  |  |
                       O  O  O  O  O  O  O
 A(---------.                                .----------)
            |                                |
            B(--------.                   .--)
                      |                   |         user-land
- - - - - - - - - - syscall - - - - - - - - - - - - - - - - -
                      |                   |         kernel
                      X     Off-CPU       |
                    block . . . . . interrupt

这种方法采用定时采样来捕获被阻塞线程的栈记录。