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 等等。

它的工作流程很简单:

  1. 初始化算法上下文
  2. 循环执行加密/解密/哈希/签名操作
  3. 记录总耗时,计算吞吐量(KB/s 或 ops/s)
  4. 打印结果到串口

我曾经在一个项目里,直接用这个程序测了 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 到解读结果,再到内存测量,环环相扣。

mbedTLS 基准测试流程 mbedTLS 基准测试核心流程 1. 编译 benchmark 2. 烧录并运行 3. 采集耗时数据 对称算法:看 KB/s 非对称算法:看 ops/s 内存占用测量 选择最优密码套件 说明: 耗时和内存必须同时测量。 只测耗时,可能忽略内存暴涨风险。 只测内存,可能忽略握手超时问题。

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。

记住:在受限设备上,性能就是安全。一个跑不动的密码套件,再安全也没用。