3、性能基准测试:在目标设备上测量加密操作的耗时与内存占用
选密码套件,不能光看理论安全性。你得在真家伙上跑一跑。
我见过不少项目,芯片选型时拍脑袋定了 AES-256-GCM,结果量产后发现握手要 3 秒。用户直接退货。所以这一章,咱们就聊怎么用 mbedTLS 自带的 benchmark 程序,把加密操作的耗时和内存占用测准。
3.1 为什么必须做基准测试
说白了,密码算法是算力大户。一个 RSA-2048 签名,在 Cortex-M4 上可能要几百毫秒。在 Cortex-M0 上?嗯,可能直接超时。
你想想看,如果设备每秒只能做 5 次 ECDHE 密钥交换,那 TLS 握手就得排队。用户体验?不存在的。
我个人习惯是:先测后选。拿到开发板的第一件事,不是写业务逻辑,而是跑一遍 benchmark。这样心里有底,知道哪些算法能用,哪些得绕开。
核心原则: 在受限设备上,密码算法的实际性能往往比理论值差 10 倍以上。基准测试不是可选项,是必选项。
3.2 mbedTLS benchmark 程序结构
mbedTLS 自带了一个 benchmark 示例程序,路径在 programs/test/benchmark.c。它几乎覆盖了所有主流算法:AES、SHA、RSA、ECC、ChaCha20、Poly1305 等等。
它的工作流程很简单:
- 初始化算法上下文
- 循环执行加密/解密/哈希/签名操作
- 记录总耗时,计算吞吐量(KB/s 或 ops/s)
- 打印结果到串口
我曾经在一个项目里,直接用这个程序测了 8 款不同型号的 MCU。结果发现,同一款 AES-128-CBC,在不同芯片上的速度能差 40 倍。嗯,这里要注意:硬件加速引擎的有无,是决定性因素。
3.3 编译与运行 benchmark
编译 benchmark 程序很简单。你只需要在 mbedTLS 源码根目录下执行:
# 进入 mbedTLS 目录
cd mbedtls
# 配置编译选项(这里以 ARM GCC 为例)
cmake -DCMAKE_TOOLCHAIN_FILE=../arm-gcc-toolchain.cmake \
-DCMAKE_BUILD_TYPE=Release \
-DENABLE_PROGRAMS=ON \
-DENABLE_TESTING=OFF \
-DMBEDTLS_SELF_TEST=ON \
..
# 编译
make -j4
# 生成的 benchmark 可执行文件在 programs/test/ 目录下
ls programs/test/benchmark
如果你用的是 Keil 或 IAR,可以直接把 programs/test/benchmark.c 加入工程,然后编译下载。
小技巧: 编译时加上 -DMBEDTLS_SELF_TEST=ON 可以启用内置自检,确保算法实现正确。我建议第一次跑的时候打开,确认无误后再关掉以节省代码空间。
3.4 运行结果解读
把 benchmark 烧进目标设备,打开串口(通常 115200 8N1),复位后你会看到类似这样的输出:
SHA-256 : 12345 次/秒 ( 9876 KB/s)
SHA-512 : 5678 次/秒 ( 4542 KB/s)
AES-128-CBC-enc : 2345 次/秒 ( 1876 KB/s)
AES-128-CBC-dec : 2340 次/秒 ( 1872 KB/s)
AES-256-GCM-enc : 890 次/秒 ( 712 KB/s)
AES-256-GCM-dec : 885 次/秒 ( 708 KB/s)
RSA-2048-sign : 12 次/秒
RSA-2048-verify : 345 次/秒
ECDHE-secp256r1 : 45 次/秒
ECDHE-curve25519 : 78 次/秒
这里有几个关键点:
- 对称算法(AES、ChaCha20):看吞吐量(KB/s)。如果低于 100 KB/s,那大数据包加密会很慢。
- 非对称算法(RSA、ECC):看每秒操作次数(ops/s)。RSA 签名通常很慢,验证快。ECC 则相反。
- 密钥交换(ECDHE):看每秒握手次数。如果低于 10 次/秒,那并发连接多了会卡死。
我记得有一次,客户说他们的设备握手太慢。我一看 benchmark 结果,RSA-2048 签名只有 5 次/秒。换成 ECDSA 后,直接飙到 80 次/秒。问题就这么解决了。
3.5 内存占用测量
耗时好测,内存占用就麻烦一点。mbedTLS 的 benchmark 程序本身不输出内存占用,但我们可以用两种方法:
| 方法 | 原理 | 精度 | 适用场景 |
|---|---|---|---|
| 静态分析 | 查看 map 文件,计算 .text + .data + .bss | 低(不含堆) | 快速估算 |
| 运行时监控 | 在 benchmark 前后调用 malloc_stats() 或 heap_caps_get_free_size() |
高(含堆) | 精确测量 |
我个人习惯用第二种。在 benchmark.c 的 main() 函数开头和结尾加上内存统计代码:
#include <malloc.h> // 或 ESP-IDF 的 heap_caps.h
int main(void)
{
// 测量前
struct mallinfo mi_before = mallinfo();
printf("Free heap before: %d bytes\n", mi_before.fordblks);
// 运行 benchmark
// ... 原有代码 ...
// 测量后
struct mallinfo mi_after = mallinfo();
printf("Free heap after: %d bytes\n", mi_after.fordblks);
printf("Peak heap usage: %d bytes\n", mi_before.fordblks - mi_after.fordblks);
return 0;
}
注意: 有些 RTOS(如 FreeRTOS)使用自己的堆管理,mallinfo() 可能不可用。这时你需要用 RTOS 提供的 API,比如 xPortGetFreeHeapSize()。
3.6 核心知识体系
下面这张图总结了本章的核心逻辑:从编译 benchmark 到解读结果,再到内存测量,环环相扣。
3.7 常见坑与避坑指南
- 优化等级影响巨大:-O0 和 -O2 的 benchmark 结果可能差 5 倍。我建议用 Release 模式(-O2)测,这才是真实性能。
- 中断干扰:如果设备在跑 benchmark 时频繁响应中断,结果会偏慢。最好在 idle 状态下测,或者关掉不必要的中断。
- 缓存预热:第一次调用算法时,可能因为缓存未命中而偏慢。mbedTLS 的 benchmark 会先跑一轮预热,但如果你自己写测试代码,别忘了加预热循环。
- 堆栈溢出:有些算法(如 RSA-4096)需要大量栈空间。我曾经遇到过 benchmark 跑到一半就 HardFault 的情况,后来发现是栈不够。建议把栈设到 4KB 以上。
我的经验: 拿到新开发板后,我会先跑一遍 benchmark,然后把结果记下来。以后换芯片或升级 mbedTLS 版本时,再跑一遍对比。这样能第一时间发现性能退化。
3.8 小结
基准测试不是走过场。它是密码套件选择的基石。
用 mbedTLS 的 benchmark 程序,你能快速拿到耗时和内存数据。然后根据这些数据,决定用 AES-128 还是 AES-256,用 ECDHE 还是 RSA,用 SHA-256 还是 SHA-512。
记住:在受限设备上,性能就是安全。一个跑不动的密码套件,再安全也没用。