4. 评测基准测试框架设计

后量子签名算法的性能评测,最容易犯的错误不是“测得不准”,而是“测得不可比”。同一份 Dilithium 实现,在不同机器、不同编译选项、不同预热策略下,签名吞吐量可能相差数倍。因此,一个合格的基准测试框架,其核心目标不是追求某个漂亮的数字,而是保证公平与可重复:换一台机器、换一个执行者、换一天重跑,结论依然成立。本节围绕测试用例生成、预热策略、多次测量与统计方法四个环节,说明如何把评测从“跑一次看看”变成“可复现的实验”。

一、测试用例生成:让输入分布可控且可复现

后量子签名的性能与输入长度、密钥规模、消息内容都有关系。以 ML-DSA(Dilithium)为例,签名时间会随消息长度增长,验签时间则主要受公钥和签名尺寸影响;而 SPHINCS+ 的签名时间对消息内容几乎不敏感,却对哈希调用次数高度敏感。因此测试用例必须显式定义三类参数:安全级别(如 ML-DSA-44/65/87)、消息长度分布(如 0 B、32 B、1 KiB、64 KiB、1 MiB)、以及密钥与随机数种子。

可复现的关键在于随机源受控。签名算法内部通常需要随机数(如 Dilithium 的 rnd 或 SPHINCS+ 的 opt_rand),若每次运行都取系统熵,测量结果会引入不可控抖动。推荐做法是:用固定种子初始化一个伪随机数生成器(PRNG),由它统一产生密钥种子、消息内容和签名随机数。这样任何人拿到种子和参数,都能重建完全相同的输入集合。下面是一个用例描述文件的示例结构:

{
  "algorithm": "ML-DSA-65",
  "seed": "0x5A17C0DE",
  "message_lengths": [0, 32, 1024, 65536, 1048576],
  "iterations_per_length": 200,
  "warmup_rounds": 50,
  "measure": "sign_and_verify"
}

需要强调的是,消息长度应覆盖“空消息”和“大消息”两个边界,因为不少实现的性能拐点出现在分块哈希的边界处。此外,密钥生成、签名、验签应作为三个独立用例分别测量,不要混在一个循环里,否则无法区分各阶段的耗时构成。

二、预热策略:排除冷启动与频率爬坡的干扰

现代 CPU 存在频率调节、缓存预热、分支预测器训练等效应。第一次调用签名函数时,代码段尚未进入指令缓存,动态库可能还未完成惰性绑定,CPU 也可能处于低频状态。若把前几次测量计入统计,均值会被显著拉高。预热的目的就是让被测对象进入“稳态”。

实践中建议采用两段式预热:第一段做固定轮数的“功能预热”,例如连续执行 50 次签名与验签,不记录时间;第二段做“计时预热”,即先跑若干轮并观察相邻轮次的中位数是否趋于平稳,若波动超过阈值(例如 5%)则继续预热。预热轮数不应写死为某个魔法数字,而应作为可配置参数记录在结果元数据中,便于他人复现。对于 SPHINCS+ 这类单次签名耗时可达毫秒级的算法,预热轮数可适当减少;对于签名仅几十微秒的 ML-DSA,则需要更多轮次才能覆盖频率爬坡区间。

预热与测量应使用同一批输入数据,但预热阶段的随机数消耗不能影响测量阶段的输入序列。推荐在预热前就把全部输入预生成并缓存,测量阶段只做索引读取,避免把 PRNG 开销混入签名耗时。

三、多次测量与统计:均值之外还要看离散程度

单次测量没有统计意义。正确做法是对每个用例重复测量 N 次(例如 200 次),得到耗时样本序列,再计算均值、标准差与置信区间。均值反映中心趋势,标准差反映稳定性;若标准差相对均值过大(例如超过 10%),说明存在系统噪声,应检查是否有后台进程、是否发生频率降频、是否触发了内存换页。

置信区间用于回答“这个均值有多可信”。在样本量较大时,可用正态近似:均值 ± 1.96 × 标准差 / √N 给出 95% 置信区间。更严谨的做法是使用 t 分布,尤其当 N 较小(如 30)时。下面给出一个可直接套用的计算示例(示例数据为合理假设,非实测):

指标符号示例值
样本数N200
签名耗时均值x̄92.4 μs
样本标准差s3.1 μs
标准误s/√N0.219 μs
95% 置信区间x̄ ± 1.96·s/√N[91.97, 92.83] μs

解读时要注意:置信区间窄只说明“均值估得准”,不代表“每次调用都稳定”。若某实现的均值 92 μs、标准差 3 μs,另一实现均值 95 μs、标准差 0.5 μs,前者均值更优但抖动更大,在实时性要求高的场景下后者可能更合适。因此报告中应同时给出均值、标准差、置信区间和样本数,缺一不可。

此外,耗时分布往往右偏(偶发的长尾延迟),此时均值会被拉高,建议同时报告中位数与 95 分位数(P95)。对于签名这类可能受垃圾回收或内存分配影响的实现,P95 比均值更能反映最坏体验。

四、框架整体结构与公平性约束

把上述环节串起来,一个可复现的基准框架应包含:参数与种子管理、输入预生成、预热控制、计时采集、统计汇总、元数据记录六个部分。下图展示了各环节之间的数据流与依赖关系,以及公平性约束落在哪些节点上。

后量子签名基准测试框架数据流 参数与种子管理 算法/安全级/消息长度 固定 PRNG 种子 输入预生成 密钥/消息/随机数 缓存为只读数组 预热控制 功能预热 + 计时预热 轮数写入元数据 计时采集 单调时钟 N 次样本序列 统计汇总 均值 · 标准差 · 95% 置信区间 · 中位数 · P95 异常样本标记(离群点剔除需记录规则) 元数据记录(可复现凭证) CPU 型号 / 编译选项 / 库版本 / 种子 / 预热轮数 测量时间 / 样本数 / 剔除规则 公平性约束(贯穿全流程) ① 同一对比组使用相同机器、相同编译选项、相同优化等级 ② 预热与测量使用同一输入集,PRNG 开销不计入签名耗时 ③ 报告必须附带样本数、离散程度与置信区间,禁止只报单次最优值

图中最容易被忽视的是右下角的“元数据记录”。很多评测报告只写“Dilithium 签名 90 微秒”,却不写 CPU 型号、是否开启 AVX2、编译器版本和样本数,这样的数字无法被任何人复现,也就失去了基准测试的意义。公平性约束则要求:对比不同算法或不同实现时,必须固定机器与编译配置;若必须跨平台比较,应分别报告各平台结果,而不是取平均。

五、常见陷阱与判断依据

第一,把密钥生成计入签名耗时。密钥生成通常远慢于签名,混入后均值失真。第二,忽略内存分配。若每次签名都重新分配缓冲区,分配开销会污染结果,应在测量循环外预分配。第三,用墙钟而非单调时钟。系统时间调整会导致负耗时或跳变,必须使用单调时钟。第四,样本量不足。N 小于 30 时置信区间很宽,结论不可靠。

判断一次评测是否合格,可以问三个问题:换一个人按报告中的种子和参数能否得到落在置信区间内的均值?报告是否给出了离散程度而不只是均值?对比结论是否在相同硬件与编译配置下得出?三个问题都能回答“是”,这次基准测试才真正具备公平性与可重复性。