小心了! Unixbench浮点运算性能压测有坑!
- 2019 年 12 月 5 日
- 筆記
背景
在测试某台服务器(非虚拟机)的基准性能时,我们发现 Unixbench 的某个性能指标低于基准值,低的还不少,有约 20%。

正常的结果本来是这个亚子的。

经过一顿排查,最后发现:
(1)睿频会提高 CPU 浮点等纯计算的性能,但是提高得不多,约 10%~20% 左右,而且可能和负载有一定关系;
(2)上述问题的本质是因为 Whetstone 这个压测程序的缺陷导致的。
虽然得到了一些初步的结果,但是有关 CPU 层面的分析和压测,都是硬核知识,这里尚未触及到皮毛,故在此抛砖引玉一下。
初步测试和分析
由于这些指标涉及的是 CPU 浮点计算性能,通过 perf 和火焰图分析,性能瓶颈不在内核态,用户态也没有异常的热点。
通过 turbostat 和 i7z 工具排查,发现该服务器存在动态睿频,幅度较大。

查看其启动参数,未添加“intel_idle.max_cstate=1 intel_pstate=disable”的参数。加入这两个启动参数之后,重启服务器,CPU的频率和状态也稳定。

再测试,得到的结果是符合预期。
这里我们补充一下此次测试的服务器 CPU 型号,如下图。

进一步测试
现在的变量就落在了开关这两个参数上。从 i7z 的监控图可以发现区别,未加参数时,CPU 默认会支持 C0/C1/C6 的Cstate 状态,C0 一般就是 CPU 核在执行指令,也就是工作状态,而当没有指令执行,CPU 会切换到 C1 甚至是 C6 状态。Cstate 的具体描述可以从 Intel 的官方手册中看到。


CPU 进入到 C6 状态时,会关闭更多的 Oncore 组件,同时也会清空 Uncore 的 cache,关闭时钟。这么做带来的好处是省电,但是同时也会带来更长的恢复时延(默认情况下,服务器都会避免出现 CState > C1 的情况以减少服务时延抖动等问题)。
难道是因为 CPU 更多的进入到大于 C1 的模式而导致 CPU 响应延迟增加,进而最终导致性能的降低吗?
实际跑测试程序时,用工具 turbostat 和 i7z 跟踪 Whetstone 运行时 CPU 的状态,奇怪的是,两种情况下,CPU 都是运行在 C0 状态,也就是说,CPU 绝大部分的状态是执行态,全速运行,因为 CPU 状态切换而导致的延迟几乎没有。另外一个现象是,开启 C6 Cstate 的服务器(以下以 C0C1C6 表示支持深度睿频设置的服务器,C0C1 表示固定睿频设置的服务器),运行压测程序的 CPU 主频反而更高( 3580 MHz > 3098 MHz)。


这里就有疑问了,按理说,参与运算的 CPU 主频越高,性能应该会更好才对,那为什么在 C0C1C6 上实测的 Whetstone 却给出了较低的分数呢?
带着疑问,我们用 perf stat 命令分别抓取了 Whetstone 程序的执行信息,统计发现较大的差异发生在用户态指令数 instruction:u,指令数差了一倍多,而执行时间上,多了 50%。


开关了上述内核启动参数,就会导致相同的执行程序在指令总上出现差异,看着像是CPU在故意捣乱,加塞了很多指令?这确实有些令人费解。但是Edwin 金总说了,perf 统计的 instruction 数据,是指 CPU retired instruction 的个数。事实确实如此,指令的多少还是主要受控于具体的程序,CPU 不会因为频率变化,产生程序之外的指令(除非是 exception 一类的,那是另外一回事了)。那么问题就应该在 Whetstone 程序了,通过分 其实现源码,我们找到了差异的原因。
初步的结果
原来 whetstone 在执行浮点计算压测之前,有一个固定的步骤是预估待测 CPU 的主频,然后估算出一个工作量(保存在 xtra 变量里)给到压测函数中,再执行压测函数,最后综合 xtra 和压测函数的耗时,得出一个比率即为 CPU 的浮点计算性能值。xtra 的取值特点是:随着预估频率的增大而增大,目的是在一个足够宽的时间段内,压测出一个相对稳定可靠的浮点计算性能值。下图就是作者的注释(居然是在奔腾系列的测试例子,这年代有点久远哈)。

这种单一的预估结果来拟合 CPU 的执行频率,也许在早期固定频率的 CPU 上使用是有效的。但是时至今日,情况已经发生了改变。目前,睿频技术已普遍应用,早在 2008 年,Intel 就在其 core i7 处理器中引入了 turbo boost 技术。

Intel 的睿频技术是在处理器功耗设计允许的范围内,短期的抬高 CPU 的核心频率,提升计算性能,但是持续时间并不会很长,因此超频出来的计算能力并不可持续,至少在秒级别是如此(目前较新的 Intel CPU 支持部分 CPU 核心的稳定睿频,这个是一个深刻的话题,待后续研究)。
现在问题很明显了,Whetstone 通过简单的预估 CPU 频率的方式,拟合出一个预估的、固定的 CPU 频率值,并转化成压测的计算量,这种方式显然没有考虑到频率可变的情况,计算的结果自然也不一致不可信。
这也不奇怪,因为 Unixbench 已经存在超过 20 年了,而 GitHub 上的 Whetstone 源码最后的提交日期是十年以前了,没有考虑到 CPU 架构变化和 turbo boost 带来的频率变化等问题也属正常。

验证
接下来,我们修改了 Whetstone 的源码,主动控制 Whetstone 的 xtra 变量,经过多次运行、采样和统计,我们得到下面两张图(横坐标是计算量 xtra,纵坐标是 Whetstone 压测程序的耗时 secs)。


首先,Whetstone 的内部实现上是包含了 8 种测试,每种测试都会输出一个时间结果绘制到上图中,用 N1~N8 表示( 其中 N4 因为是整形计算,结果忽略)。上图在相同计算量下的耗时比较来看,支持更高的 turbo boost 睿频的CPU耗时更短,计算性能是更佳的。
其次,在计算量较小,如图横坐标为 4500 以下时,所有测试的结果(耗时)都是线性的,而当横坐标超过 4600 之后,情况发生了转变,N8(sqrt/exp/log)压测的时长出现较大幅度的增长,影响了总体压测耗时的线性关系。这个问题是个还需要进一步分析,目前估计是和计算的数值范围导致处理的代码走入另外的分支,代码量变化有关系,具体还要测试和看看 glibc/math 库的实现。
在此文所述的案例中,C0C1C6 实际的计算性能是更出色的(单核情况下)。但是由于Whetstone在启动阶段预估的 xtra 变量比 C0C1 时的更大,导致其运行的计算指令数更多;另外一方面,Whetstone 最终针对 C0C1C6 设置的 xtra 约为 5700,而 C0C1 的 xtra 约为 4800,此时已经落入了上图的非线性区,C0C1C6 得出的压测结果实际上是偏低的。