CPU那些事儿 - CPU Benchmarks(下)

627 阅读10分钟

背景

上一篇里我们介绍了SoC厂商常用的几个用于分析性能和功耗的Benchmarks,本篇我们继续介绍SPEC CPU和Geekbench这两个更常见的性能测试集。

SPEC CPU

SPEC(Standard Performance Evaluation Corporation)CPU 是目前业界最权威的CPU性能基准测试之一,主要用于评估处理器在计算密集型负载下的执行效率,SPEC CPU测试集由于其测试的全面性和专业性,更加偏重于衡量CPU微架构提升,因此我们常常看到在ARM发布新的架构产品时常常使用SPEC CPU配合Dhrystone用来展示微架构在不同频点的性能和功耗表现:

arm_spec.png SPEC CPU当前主要有两个版本,SPEC CPU2006和SPEC CPU2017,尽管 SPEC CPU2006 曾长期作为行业标准,但随着硬件架构和软件应用的演进,它的局限性逐渐显现。为应对现代计算需求,SPEC 于 2017 年推出了新的版本 — SPEC CPU2017。二者在设计目标、测试程序、评分机制等方面存在较大差异,具体如下:

测试程序的更新与拓展

项目SPEC CPU2006SPEC CPU2017
子测试数量29 个 benchmark43 个 benchmark
工作负载类型主要是 C 语言应用(科学计算、编译器等)覆盖更广,包括 C/C++/Fortran,新增图像处理、金融建模等现代负载
工作负载规模相对较小更复杂、运行时间更长,贴近现代服务器负载

CPU2017 中删除了一些陈旧负载(如 400.perlbench、403.gcc),引入了更多现代代码结构和内存访问模式。

测试模式和模式设计变化

特性CPU2006CPU2017
模式分类int 和 fp 两类测试,默认单线程明确划分为 SPECspeed(单线程)与 SPECrate(多线程),各自再分 int/fp
评分维度单一分数,INT 和 FP 分开给出四类指标(speed/fp、speed/int、rate/fp、rate/int),更细化
性能衡量单位每个 benchmark 的执行时间与参考系统对比与参考系统的性能比值,体现吞吐量或单核执行速度

同时,在技术支持和平台适配方面:

  • SPEC2006 已停止官方支持(2021年之后不再接受结果提交)
  • SPEC2017 引入严格运行验证机制,更难作弊
  • SPEC2017 更加注重线程调度、缓存压力、NUMA 行为和 TLB 访问模式,贴合现代微架构特征
  • 支持更多主流操作系统和架构平台,包括 ARM64、RISC-V(部分社区移植)

因此,总体看来,SPEC2017 是对SPEC2006 的全面升级,不仅增加了负载多样性、强调现代代码行为,也更适应当前多核架构的发展趋势。对于现代处理器性能评估、芯片微架构验证、软件性能调优等任务,SPEC2017 提供了更具代表性与可扩展性的工具平台。SPEC2017分为两个子集,包括:

  • SPECspeed 2017:评估单线程性能,测量计算机在单位时间内的单个事务处理时长,测试分数高,说明单次运行时间短,CPU处理性能高。
  • SPECrate 2017:评估多线程吞吐量性能,测量计算机在单位时间内的并发事务处理能力,测试分数高,说明CPU吞吐高。

每个子集有分别包括Integer和Float类型的测试Benchmarks,涵盖数学建模、图像处理、物理仿真、编译器设计等 43 个项,全部为真实世界中的计算密集型应用。

specint.png specfp.png SPEC 2017 Speed的分值计算公式:

Ratio=Reference_TimeTest_TimeRatio = \frac{Reference\_{Time}}{Test\_Time}
  • Reference_Time: SPEC 给出的参考时间;
  • Test_Time: 运行机器测试时间;
  • Ratio:单个Benchmark子项得分,越高表示完成同样问题用时更少;

SPEC 2017 Rate的分值计算公式:

Ratio=copies(Reference_TimeTest_Time)Ratio = copies * (\frac{Reference\_Time}{Test\_Time})
  • copies:指定相同问题的运行次数;
  • Reference_Time:SPEC给出的参考时间;
  • Test_Time:运行机器测试时间;
  • Ratio:单个Benchmark子项得分,越高表示相同时间可以处理更多的问题;

从上面可以看到,Rate模式允许设置单个Case的运行次数,从笔者实际抓取Trace分析,设置的copies数量会触发多核运行,根据系统的动态负载调度行为看,优先在性能高的核上运行,以Android 的ARM平台为例,当设置的copies大小超过超大核+大核数量时,会触发在小核上运行,而Rate的运行时间是第一个copy到最后一个copy运行完的时间,因此,此时受小核性能的拖累,整体的性能结果相对更差。

Geekbench

Geekbench 是由 Primate Labs 开发的一款跨平台 benchmark 工具,相对于SPEC CPU需要运行几十个小时才能得到结果而言,它更轻量化,更加适合移动设备平台,同时它也是很多SoC厂商发布芯片时用来做评估性能最常用的跑分指标,它将测试负载划分为:

  • 单核性能测试(Single-Core)
  • 多核性能测试(Multi-Core)

测试场景模拟现实中的常见任务,如图像处理、机器学习、压缩、加密等,整体运行时间短,便于快速对比。Geekbench当前常用的同样有两个版本v5和v6,Geekbench v5是2019年发布的,v6则是2023年发布的升级版本,Geekben v6更加贴近现代应用的典型计算场景,专注于 现实任务模拟多核调度行为建模,而不仅是 synthetic 的纯算法测试。国内的很多移动SoC测试平台,如极客湾就是用它来进行移动SoC性能比较:

jkw.png

版本主要变化

项目Geekbench 5Geekbench 6
测试场景数量21 个 workload(整数/浮点)增加到 24 个,涵盖更多 AI、图像处理场景
任务模拟方式合成(synthetic)为主强调实际任务(real-world simulation)
新增任务-人脸检测、背景模糊、HDR 合成、光流跟踪、脚本渲染等
数据规模固定输入大小更动态地调整输入大小以匹配现代设备的能力
图像输入类型主以灰度图为主改为彩色图像处理,更接近真实 App 工作流

从上面可以看出,Geekbench v6 明显加强了对图像处理、多媒体、AI 前处理任务的模拟,例如卷积处理、人脸检测等场景变得更真实;同时,在多核调度方面,Geekbench v5 的多核测试是通过简单的任务分发方式进行的,对调度器、cache 协同作用、混合架构(如 ARM big.LITTLE)的模拟并不精确。而 Geekbench 6 对此进行了以下优化:

  • 更细粒度的任务划分:任务能更好地分布在高性能核与能效核之间,提升对 big.LITTLE 和 P-Core/E-Core 的表现感知。
  • 工作集大小动态调整:可根据设备性能自动扩展任务粒度,使得高端设备能更好展现能力,低端设备不至于过载。

这意味着 Geekbench 6 的多核分数对真实应用场景的系统调度器行为更加敏感,更能反映 SoC 设计优劣。

在得分标准机制上:

项目Geekbench 5Geekbench 6
打分标准化方法按每个 workload 计时取比值加入 workload 复杂度权重,更偏向现实任务性能
打分稳定性略受系统负载波动影响更稳定,得益于更长运行时间和复杂度调整

下面以Geekbench v5举例介绍,其测试集分为3类:加密(Crypto)、整型(Integer)和浮点(Floating Point),总得分根据3个子集的分数加权求和得到:

Ratio=Crypto5%+Integer65%+Floating30%Ratio = Crypto * 5\% + Integer * 65\% + Floating * 30\%
  • Crypto只有1个Case,AES加解密算法,所以Crypto的分数就是AES Case的分数,即:
Crypto=AESCrypto = AES
  • Integer(主要包括压缩、文本处理、图像滤波等)分数等于所有9个子项Case分数的几何平均数,即:
Integer=Integer1Integer2...IntegerNNInteger = \sqrt[N]{{Integer_1}*{Integer_2}* ... *{Integer_N}}
  • Floating(主要包括图像模糊、边缘检测、FFT、卷积等)分数等于所有11个子项Case分数的几何平均数,即:
Floating=Floating1Floating2...FloatingNNFloating = \sqrt[N]{{Floating_1}*{Floating_2}* ... *{Floating_N}}

因此,这里衍生出一个Geekbench调优的思路,由于数值越低的Case对几何平均数的影响越大,所以从单个Case对总体性能的贡献考虑,Geekbench想要做得分优化的优先级是:

Integer(0.659)>Crypto(0.051)>Floating(0.311)Integer(\frac{0.65}{9}) > Crypto(\frac{0.05}{1}) > Floating(\frac{0.3}{11})

测试Case说明

下面我们以Integer类型中的Navigation Case举例说明:

sequenceDiagram
	participant Main Function
	participant Console Main
	participant Benchmark Driver
	participant Workload Factory
	participant DijkstraWorkload
	Note right of DijkstraWorkload: 以Navigation测试Case为例
Main Function->>Console Main: Assert::load()加载Geekbench.plar资源
Console Main->>Benchmark Driver: Geekbench_Startup()初始化
Benchmark Driver->>+Workload Factory: BenchmarkDriver::driver调用run_workloads()
activate Workload Factory
Note right of Workload Factory: 记录reference_timer->start开始时间戳
Workload Factory->>Workload Factory: 
deactivate Workload Factory
Workload Factory->>DijkstraWorkload: workload_run()通过映射表CPUWorkloadFactoryMap查找对应的case
activate Workload Factory
activate DijkstraWorkload
Note right of DijkstraWorkload: Navation是一个计算导航负载的case,需要加载默认的安大略省地图资源navation-ontario.json
DijkstraWorkload->>DijkstraWorkload: run_with_thread_pool
deactivate DijkstraWorkload
activate DijkstraWorkload
DijkstraWorkload->>DijkstraWorkload: 遍历执行work每个run
deactivate DijkstraWorkload
activate DijkstraWorkload
DijkstraWorkload->>DijkstraWorkload: 每个线程执行完记录时间
deactivate DijkstraWorkload
activate DijkstraWorkload
DijkstraWorkload->>DijkstraWorkload: compute_result计算workload的rate
Note right of DijkstraWorkload: 1、循环n次获得n个runtime
Note right of DijkstraWorkload: 2、runtime0作为预热数据不参与计算
Note right of DijkstraWorkload: 3、计算剩余n-1次runtime的最大值、最小值、平均值和中位值
Note right of DijkstraWorkload: 4、compute_work/runtime平均值获得rate结果
deactivate DijkstraWorkload
DijkstraWorkload->>Workload Factory: 记录reference_timer->stop结束时间戳
activate Workload Factory
Workload Factory->>Workload Factory: workload_node->score
Note right of Workload Factory: 计算总分数
deactivate Workload Factory

根据上面的测试流程,我们可以发现:
1、Navigation测试选用安大略省地图数据作为测试源,地图数据包含216548个地点和450277条点到点的路径,同时固定选择其中13个地点作为测试点;
2、多核测试时会创建和CPU core数量一致的thread,每个thread执行相同的测试内容,即:

  • 第1个点作为起点,第2个点作为终点计算最短路径;
  • 第2个点作为起点,第3个点作为终点计算最短路径;
    ... ...
  • 第12个点作为起点,第13个点作为终点计算最短路径;

3、采用Dijkstra算法计算两点的最短路径:

从测试流程和Dijkstra算法本身看,计算需要占用比较大的内存,通过在高通SM8650上运行Navigation测试时获取的perf数据如下:

Crypto指令Integer指令Float指令Load/Store指令
0.00%31.71%0.76%67.53%
Perf PMUValue
raw-cpu-cycles2.919 G/sec
raw-inst-retired1.634 G/sec
raw-l3d-cache61.038 M/sec
raw-l3d-cache-refill11.169 M/sec
raw-stall-backend-mem1.502 G/sec

可以看到,IPC值比较低只有0.56 inst/cycle,说明该case并非计算密集型应用,而同时Load/Store指令占比非常大并且Cache Miss率18.3%,说明Navagation访存操作非常多,最后CPU内部微架构后端memory阻塞占比51.5%,说明对内存带宽和延迟要求比较高,核数越多并行的访问量越高,对内存带宽和延迟更加敏感,因此属于一个memory bound的测试子集。

优化策略

gkb.png 上面的Trace和我们分析的一致,每一轮测试需要等待其他core上面的case都跑完才进行下一轮测试,这里注意一下两轮测试之间的间隔时间,Geekbench v5在Apple平台上测试这个间隔时间是大于Android平台的,间隔时间并不影响性能计算结果,但是却对功耗有明显的影响,因此如果从性能/功耗方面考虑的话,Apple平台的这个优化是有非常大的收益的,而在最新的Geekbench v6上面,这个间隔时间差异,已经基本不存在了。

另外,结合上面的理论分析和Trace结果,我们主要从软件调度的方面考虑下Geekbench如何进行性能优化,首先Geekbench本身的运行逻辑是:

  • 准备阶段结束时,按照检测到系统中online cpu core个数生成对应数量的测试task线程;
  • 每个测试case运行5轮,待本轮所有task都运行完成后才统一唤醒进行下一轮测试,从而不会直接出现某些task直接跑完5轮测试的情况;

因此,可以得到一个优化调度的原则:

  • 每个task分散到不同core上执行,避免出现空闲core,减少task在不同core之间的迁移;
  • ARM系统中不同core存在性能差异,当性能强的core上task执行完成后,将性能弱的core上的task迁移过来继续执行,缩短本轮所有任务的完成时间;

由于Geekbench受源码商业保护,这里就不展示优化后的结果,感兴趣的同学可以和我们单独沟通了解。

PS:本文同步在微信公众号和知乎同步发布

公众号二维码.jpg

参考引用

  1. arxiv.org/pdf/1910.00…
  2. ieeexplore.ieee.org/document/83…
  3. www.socpk.com/
  4. www.geekbench.com/blog/2025/0…