简介:Dhrystone Benchmark 2.1 是一套面向嵌入式/MCU开发者的经典处理器性能测试程序,用于快速评估CPU的整数运算能力,经常作为MCU选型与微架构对比的参考依据。压缩包内共51个文件,以C语言源代码(.c)为主体,附有头文件(.h)、汇编文件(.asm)、目标文件(.o)以及多种可执行程序和README说明,整体仅166KB,体积小巧便于集成到各类工程中。资源不仅包含Dhrystone原版2.1源码,还整合了Whetstone、Linpack、Livermore Loops等经典基准测试项,并提供32位/64位编译目录、优化与非优化编译版本以及runall运行脚本,可一键编译并运行测试,也便于观察编译器优化对得分的影响。目前已有573人学习下载,对需要获取处理器基准数据或进行MCU横向对比的开发者而言,是一份实用且完整的测试参考包,尤其适合嵌入式软件工程师与硬件选型人员使用。
1. 项目核心认知:为何要用Dhrystone量化CPU性能
每次拿到一颗新芯片,无论是MCU还是应用处理器,我最先做的事往往不是看主频,而是先跑一遍Dhrystone。这个习惯在嵌入式行业里很普遍,但也常被误解——有人觉得它过时,有人直接用跑分当最终结论,两种态度都偏了。
1.1 Dhrystone在性能评估中的实际定位
Dhrystone诞生于1984年,创作者是Reinhold Weicker。它的本质是一段精心设计的合成负载程序,专门用来压测CPU的整数运算、字符串复制、指针寻址、条件分支和过程调用等高频操作。相比直接跑一套真实应用,它不依赖具体的操作系统、库函数或编译框架,移植性强,几分钟就能出结果,非常适合在芯片评估阶段、内核裁剪阶段或架构选型初期快速判断一个核心的整数处理能力。
这里要强调一个容易搞混的点:Dhrystone测出的“MIPS”并不是指令集文档里那个“每秒百万条指令”。准确写是DMIPS,全称Dhrystone MIPS,以VAX 11/780机器作为参考基准。当年VAX 11/780跑Dhrystone 1.1版大约是每秒1757次迭代,被定义为1 DMIPS。所以新版V2.1算出来的结果,要先除以1757换算成“等价VAX MIPS”,再除以主频才是我们常说的DMIPS/MHz。这个换算关系网上很多人写错,后面实操部分我再细说。
1.2 为什么是Version 2.1而不是其他版本
Dhrystone有两个大版本:1.x和2.x。2.0版本虽然修正了1.x里的一些统计和输出问题,但仍有代码结构上的缺陷,比如过程调用次数统计不准确、部分代码可能被优化器整段消除。2.1版本在1988年发布,修复了这些坑,并把运行逻辑重新整理成一套更稳定的循环结构。从那时起,几乎所有芯片厂商、编译器文档和RTOS测评里提到的Dhrystone,指的都是V2.1。
我常把V2.1比作一把经久耐用的钢尺,做工不算花哨,但刻度清晰、不缩水。用它量出来的数字,几十年横向可比。这也是为什么哪怕CoreMark这类新基准已经普及,我还是建议任何人入行时先把Dhrystone 2.1跑通、吃透。理解它的原理,再去看CoreMark结果,你才不会被一堆花哨参数带偏。
2. 内核机制拆解:Dhrystone V2.1到底在测什么
V2.1的完整C源码不长,核心部分大约一百多行,但涵盖的指令类型相当全面。了解这些细节,不仅是为了懂跑分,更是为了明白“这个分数到底能说明什么、不能说明什么”。
2.1 七类负载的操作比例与统计逻辑
Dhrystone V2.1内部定义了八个全局数组、若干字符串变量和过程函数,以循环方式反复执行一组固定的操作序列。按Weicker最初的统计,整份负载里各类操作占比大约是:
| 操作类型 | 典型占比 | 覆盖的指令特征 |
|---|---|---|
| 赋值语句 | 约35% | 寄存器与内存间数据传输 |
| 过程调用 | 约15% | 栈操作、跳转、返回地址预测 |
| 条件判断 | 约20% | 分支预测、标志位处理 |
| 整数算术 | 约12% | ALU流水线效率 |
| 字符串操作 | 约8% | 字节寻址、循环展开潜力 |
| 数组寻址 | 约6% | 地址计算、缓存访问模式 |
| 指针间接引用 | 约4% | 访存延迟、别名判断 |
从这个表能明显看出问题:Dhrystone里的浮点运算几乎可以忽略,而且工作集非常小,全部数据能轻松放进现代CPU的L1缓存。所以它测的是处理器核心的流水线效率、分支处理能力和编译器的代码生成水平,跟内存带宽、缓存容量、浮点单元这些关系不大。换句话说,它测的是“CPU核心本身的底子”,而不是“整台机器跑大程序的能力”。
2.2 V2.1对优化器“下套”,阻止死代码消除
V2.1最核心的工程智慧在于它对编译优化的防御机制。早期版本里,某些全局变量如果仅用于计算而不输出,优化器会把整段代码当成“死代码”删掉,导致跑分结果虚高。V2.1在每个循环周期结束前,会把当前计算得到的一组结果写入几个固定的全局变量,这些变量在程序最后会被打印出来。编译器无法证明“没人读这些变量”,因此必须保留所有中间计算。
这带来一个很关键的实操结论:跑Dhrystone时,不要关闭编译器优化,也不要为了“公平”强制加-O0。加-O0跑出来的DMIPS/MHz通常比-O2要低30%以上,而它并不能代表真实使用场景,因为没人会拿-O0去发布产品。官方推荐的常规做法是打开用户实际使用的优化级别,通常就是-O2或尺寸优化-Os。这样得出的分数才是“真正常规编译条件下这颗核心的表现”。
3. 实操全流程:从源码到有效跑分
这一节我直接给出一套我验证过很多次的完整流程,覆盖环境准备、编译、计时、计算四个环节。读完之后,你完全可以自己复现一遍,并能分辨网上大多数跑分帖是否靠谱。
3.1 获取源码与最小系统准备
Dhrystone V2.1源码在很多编译器套件里自带,比如老版本GCC的benchmarks目录、ARM的DS-5安装包,以及各种MCU SDK的demo目录。如果找不到现成的,可以到一些持续维护的开源仓库里搜dhrystone 2.1,注意核对源码头部注释里的版本号,确保是1988年之后的版本。一套能用的环境只需要三样东西:
- 一个C编译器(GCC、Clang、IAR、Keil、armcc均可)
- 一个可运行的裸机或RTOS环境(Linux环境也可以,但需要时钟精度足够的计时函数)
- 一个能输出字符的串口或终端,用于拿到最终打印出来的计时循环次数
如果你是在MCU上跑,建议把代码放在内部Flash里、数据放内部RAM,避免外部总线干扰结果。如果是在Linux主机上跑,关掉后台无关负载,最好绑核运行,防止进程迁移影响计时稳定性。
3.2 编译、运行与DMIPS计算
复制代码的时候注意,V2.1官方源码通常包含dhry.h、dhry_1.c、dhry_2.c和cp.c(计时函数)。下面是一份在Linux环境下的编译和运行示例:
gcc -O2 -o dhry dhry_1.c dhry_2.c cp.c -I. # 运行,指定循环1000000次,打印结果 ./dhry 1000000程序输出的核心内容大致长这样:
Dhrystones per Second: 363636 DMIPS: 207.0看到这个数字先别急着抄进PPT,要确认两件事:第一,程序实际运行时间是否超过10秒。如果循环次数设少了,计时误差会很大。V2.1的计数方式是读取程序运行的总时间,再除以循环次数求平均,如果总时间只有几百毫秒,系统调用的抖动会直接污染结果。我一般会把循环次数调到让整个跑分在20到30秒之间,这个区间稳定性和效率都很好。
第二,程序打印的DMIPS是源码里已经算好的,公式是Dhrystones per Second / 1757。我要提醒一句,有些移植版本源码里的HZ(计时器频率)定义不正确,导致算出来的每秒迭代次数差很多。所以我在拿到任何板子的跑分前,都会用秒表实测一次总耗时,再手动核验:
// 编程风格的计算过程 double dmips_per_second = run_seconds / loop_count * 1757; double dmips_per_mhz = dmips_per_second / cpu_mhz;举例:一颗MCU跑100万次循环,实测耗时11.2秒,频率64MHz,那么每秒迭代约89285.7次,DMIPS约50.8,DMIPS/MHz约0.794。如果你手册上标的是“0.79 DMIPS/MHz”,基本对得上,说明数值可信。如果实测比手册低得离谱,可以先检查是不是开了调试器、代码在RAM里跑、或者Flash等待周期没配置好。
4. 避坑实录:跑Dhrystone时最常见的翻车点
这部分我踩过的坑比较多,挑出最有代表性的几类,给新手省点时间。
4.1 编译优化变动导致分数异常波动
最常见的“误报”来源是编译器版本差异。同一个芯片,用GCC 9和GCC 12分别-O2编译,Dhrystone分数可能差10%到15%。这是因为新版编译器对字符串复制、数组循环的自动向量化能力更强。所以做跨处理器对比时,尽量用同一套工具链、同一优化选项,否则你比的其实是编译器,不是硬件。
还有一次,我在移植某个RTOS时,demo默认开启-ffunction-sections -fdata-sections配合--gc-sections链接选项,Dhrystone分数比不开时高出不少。原因是链接器把未调用的冗余段全部丢掉了,代码密度提升,指令缓存命中率变高。这个不是程序本身变快了,是镜像变小了。遇到这种差异,要主动去翻编译器的优化日志,别抱着数字傻乐。
4.2 计时源选择不当与频率换算错误
在很多MCU工程里,cp.c里的计时函数默认依赖systick这类操作系统节拍,默认节拍一般是1ms或10ms。如果循环体跑得极快,两次计时边界的分辨率不够,最终每秒迭代次数的误差能轻松超过5%。我常用的做法是改用DWT->CYCCNT,直接数核心时钟周期,精度高到可以忽略不计。
频率换算上又有一个经典错误:有人用SystemCoreClock算出的MHz,但实际运行时锁相环还没切换完成,主频其实只有初始HSI值,最终DMIPS/MHz算出来虚高。最稳妥的办法是跑分前从串口打印当前的SystemCoreClock,确认和预期一致再算比值。
4.3 用Dhrystone去对比不同架构时的误读
这是我最想劝退新手的一点:不同指令集架构之间直接比Dhrystone绝对分数,意义不大。一颗Cortex-M4在168MHz跑出1.25 DMIPS/MHz,一颗Cortex-A53在1.5GHz跑出2.3 DMIPS/MHz,看起来A53显然更强,但两者应用场景完全不同。Dhrystone的负载模型更像“只考验单核顺序能力的微架构测试”,对乱序执行、分支预测器容量、缓存层级并不敏感。所以跨架构对比时,你只能看“同频率下核内效率”的差异,不能直接换算成应用性能。真正选型时,我建议把Dhrystone当成初筛,再搭配CoreMark和真实应用代表段综合判断。
5. 从Dhrystone延伸:Benchmark方法论的通用规律
经常有人搜“domain name server benchmark”或“usb flash benchmark”,本质上都是同一套评价逻辑:用一个标准化负载去度量特定系统的性能边界。Dhrystone只是这套思维在CPU领域的始祖级代表。跑过它之后再上手其他基准,你会发现自己看问题的方式会完全不一样。
5.1 基准测试的“三问”清单,任何领域通用
无论你接下来要测DNS服务器还是U盘,我都建议先回答三个问题:第一,负载模型是否接近真实使用场景;第二,控制变量是否到位(硬件、软件、配置是否一致);第三,统计分析是否可信(是否多次测量、是否有异常值)。Dhrystone对应第一问时明显偏“合成分数”,所以必须与其他基准配合;DNS benchmark则要关注并行查询数、缓存命中率、包大小分布这些真实变量;U盘测速则要区分持续读写和4K随机,不能只跑一个顺序大文件。想通这一步,Benchmark就不再是简单跑分,而是一套严谨的工程方法论。
5.2 给新人的一条实用建议
从我自己的经验看,入门阶段最有效的做法是:先复现,后质疑,再定标。复现标准结果的整个过程,会让你对工具链、板级初始化、计时机制有第一手的认知;之后去质疑“为什么这个数值和手册有出入”,会让你主动排查到代码配置、Flash延迟等容易忽略的细节;最后把一个已知期望值固化到自己的构建脚本里,当作每次新编译器的回归检查项。这样做习惯后,你再遇到任何性能问题,第一反应就不是“感觉卡”,而是“跑个数据看看”——这才是做工程的基本功。
回到Dhrystone本身,我个人的体会是:它像一个老练的体检医生,能快速告诉你“核心底子是否有明显问题”,但不会替代你做全面体检。用它的长处,别指望它解决所有问题,这套思路对一切benchmark都适用。
本文还有配套的精品资源,点击获取