更多互联网精彩资讯、工作效率提升关注【飞鱼在浪屿】(日更新)读取时间需要多长时间?早在 2014 年 Netflix 将服务从 CentOS Linux 切换到 Ubuntu 时,这些奇怪的问题就浮出水面,出现了几个奇怪的性能问题,包括本文描述的一个问题。虽然你不太可能再遇到这个特定问题,但有趣的是这种类型的问题和调试它的简单方法:使用可观察性和实验工具的实用组合。Cassandra 数据库集群已切换到 Ubuntu,并注意到写入延迟增加了 30% 以上。对基本性能统计数据的快速检查显示 CPU 消耗增加了 30% 以上。Ubuntu 到底在做什么,导致 CPU 时间增加了 30%!?1. 命令行工具Cassandra 系统是 EC2 虚拟机 (Xen) 实例。我登录并使用了一些基本的 CLI 工具开始使用。是否有其他程序消耗 CPU,例如 CentOS 中没有的行为不端的 Ubuntu 服务?top(1) 显示只有 Cassandra 数据库在消耗 CPU。https://www.brendangregg.com/Articles/Netflix_Linux_Perf_Analysis_60s.pdf生命周期较短的进程(例如在循环中重新启动的服务)呢?这些对 top(8) 来说是不可见的。execsnoop(8) 工具什么也没显示。看起来额外的 CPU 时间确实在 Cassandra 中,是咋样的呢?2. CPU 配置文件通过比较CPU 火焰图应该很容易理解 CPU 时间。CentOS 和 Ubuntu 的实例并行运行,可以同时收集火焰图(同一时间的流量组合)比较它们。CentOS 火焰图:Ubuntu 火焰图:没有 Java 堆栈——应该有一堆绿色的 Java 方法——而是只有一两个绿色框架。这就是 Java 火焰图当时的样子。那年设计了 c2 帧指针修复,-XX:+PreserveFramePointer,修复了这些配置文件中的 Java 堆栈。即使有损坏的 Java 堆栈,这里一个很大的不同:在 Ubuntu 上,libjvm 调用中有大量的 CPU 时间:os::javaTimeMillis()。火焰图中间的 30.14%。该服务器花费了大约三分之一的 CPU 周期来检查时间!这是一个奇怪的问题:获取时间本身现在已经成为性能分析的资源和目标。3. 研究火焰图os::javaTimeMillis 获取当前时间。浏览火焰图显示它正在调用进入 tracesys() 和 syscall_trace_enter/exit() 内核函数的 gettimeofday(2) 系统调用。这提供了两个信息:A)在 Ubuntu 中启用了一些系统调用跟踪(审计?apparmor?)。B) 在 Ubuntu 上获取时间有点慢,这可能是库更改或内核/时钟源更改。理论 (A) 可能是因为火焰图中的帧宽度。但不完全确定。此配置文件是使用 perf(1) 和内核的软件 cpu-clock 软中断收集的,而不是硬件 NMI。如果没有 NMI,则无法分析某些内核代码路径(禁用中断)。此外,由于它是 Xen 来宾,因此永远无法分析管理程序时间。这两个因素意味着火焰图中可能缺少内核和管理程序时间,因此 os::javaTimeMillis 中的真正时间细分可能会不同。请注意,Ubuntu 也有一个框架来显示进入 vDSO(虚拟动态共享对象)。这是一个用户模式系统调用加速器,gettimeofday(2) 是 vdso(7) 手册页中的经典用例。当时,Xen pvclock 源不支持 vDSO,所以可以在 vdso 框架上方看到 syscall 代码。它在 CentOS 上是一样的,尽管它在火焰图中不包含 vdso 框架(我猜是因为 perf(1) 的差异)。4. 同事/网络问的其他人没有遇到这个问题,当时互联网上没有任何使用搜索词 os::javaTimeMillis、clocksource、tracesys()、Ubuntu、EC2、Xen 等的内容。5. 实验为了使用可观察性工具进一步分析这一点:修复 Java 堆栈以查看在 Ubuntu 上使用时间的方式是否有所不同。也许 Java 出于某种原因更频繁地调用它。跟踪 gettimeofday() 和相关的系统调用路径,以查看是否存在差异。但正如我在什么是可观察性帖子中总结的那样,可观察性一词可以提醒人们不要陷入那种类型的分析。这里有一些我也可以探索的实验方法:禁用 tracesys/syscall_trace。两个系统上的性能测试 os::javaTimeMillis()。尝试更改内核时钟源。6.测量获取时间的速度是否已经有 os::javaTimeMillis() 的基准测试?这将有助于确认这些调用在 Ubuntu 上确实变慢了。找不到这样的微基准,所以写了一些简单的东西。我不会在时间之前和之后进行时间调用来计时持续时间。相反,在循环中调用 time 数百万次并计算它需要多长时间。$ cat TimeBench.java
public class TimeBench {
public static void main(String[] args) {
for (int i = 0; i < 100 * 1000 * 1000; i++) {
long t0 = System.currentTimeMillis();
if (t0 == 87362) {
System.out.println("Bingo");
}
}
}
}这会调用 1 亿次 currentTimeMillis()。然后我通过 shell time(1) 命令执行它,为这 1 亿次调用提供总体运行时间。(循环中还有一个测试和 println() 以防止编译器优化空的循环。这会稍微减慢此测试的速度。)尝试一下:centos$ time java TimeBench
real 0m12.989s
user 0m3.368s
sys 0m18.561s
ubuntu# time java TimeBench
real 1m8.300s
user 0m38.337s
sys 0m29.875s每次调用多长时间?假设循环由时间调用主导,那么在 Centos 上大约为 0.13 us,在 Ubuntu 上大约为 0.68 us。Ubuntu 慢 5 倍。用C重写了这个,直接调用了gettimeofday(2):$ cat gettimeofdaybench.c
#include <sys/time.h>
int
main(int argc, char *argv[])
{
int i, ret;
struct timeval tv;
struct timezone tz;
for (i = 0; i < 100 * 1000 * 1000; i++) {
ret = gettimeofday(&tv, &tz);
}
return (0);
}我用-O0编译它以避免丢弃循环。在两个系统上运行此程序看到了相似的结果。这样的简短基准测试,可以反汇编生成的二进制文件,并确保编译后的指令符合预期,并且编译器没有搞砸。7.时钟源实验第二个实验是改变时钟源:$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource
xen tsc hpet acpi_pm
$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource
xen好的,所以它默认为 xen,我们在火焰图中看到了它(塔以 pvclock_clocksource_read() 结束)。试试 tsc,它应该是最快的:# echo tsc > /sys/devices/system/clocksource/clocksource0/current_clocksource
$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource
tsc
$ time java TimeBench
real 0m3.370s
user 0m3.353s
sys 0m0.026s变化是立竿见影的, Java 微基准测试现在比以前快 20 倍以上!(并且比 CentOS 快近 4 倍。)现在它已达到 33 ns,循环指令可能会夸大这个结果。如果想要更高的准确性,部分展开循环,让循环指令变得可以忽略不计。8. 解决方法时间戳计数器 (TSC) 时钟源很快,因为它仅使用 RDTSC 指令检索时间,并且使用 vDSO,它可以在没有系统调用的情况下执行此操作。由于担心时间误差,TSC 传统上不是默认设置。基于软件的时钟源可以解决这些问题并提供准确的单调递增时间。碰巧在一个技术会议上,向处理器工程师提到了正在做的事情。他说 tsc 多年来一直很稳定,任何关于避免使用它的说法都是过时的。问他是否知道有公开这个这样说,但他不知道。在生产中尝试 tsc 作为解决该问题的方法。生产图中的变化很明显,显示写入延迟下降:经过更广泛的测试,它显示写入延迟下降了 43%,性能略好于 CentOS。Ubuntu 的 CPU 火焰图现在看起来像:os::javaTimeMillis() 现在总共是 1.6%。请注意,它现在显示“[[vdso]]”,仅此而已:上面没有内核调用。9. 后果我向 AWS 和 Canonical 提供了详细信息,然后在迁移过程中转移到其他性能问题上。一位同事 Mike Huang 也为 Netflix 的另一项服务点击了这个,并启用了 tsc。我们最终在 BaseAMI 中为所有云服务设置了它。那年晚些时候(2014 年),来自 AWS 的 Anthony Liguori 进行了re:Invent 演讲,建议用户将时钟源切换到 tsc 以提高性能。我还在我的演讲和我的 2015 Linux 可调参数帖子中分享了设置时钟源。多年来,关于虚拟机中时钟源的文章越来越多,现在已经是一个众所周知的问题。亚马逊甚至提供了官方推荐(2021):“对于在 AWS Xen Hypervisor 上启动的 EC2 实例,最好使用 tsc 时钟源。其他 EC2 实例类型,例如 C5 或 M5,使用 AWS Nitro Hypervisor。AWS Nitro Hypervisor 的推荐时钟源是 kvm -时钟。”Nitro 的情况发生了变化,时钟源的速度要快得多。2019 年,测试了 kvm-clock,发现它只比 tsc 慢 20% 左右。这比 xen 时钟源要好得多。不确定英特尔是否曾经发布过一些内容来阐明新处理器上的 tsc 稳定性。JMH 基准测试套件现在还可以测试 System.currentTimeMillis(),因此不再需要自己做构造测试。10. 总结读取时间本身可能成为某些时钟源的瓶颈。多年前在 Xen 虚拟机来宾上的情况要糟糕得多。对于 Linux,推荐更快的 tsc 时钟源,尽管不是处理器供应商,所以无法保证 tsc 时钟漂移问题。至少 AWS 现在已将其包含在建议中。