Nanopb 的陷阱及其避免方法

1. 引言

Protobuf 是一种与语言无关、与平台无关的可扩展机制,用于序列化结构化数据协议。Protobuf最初由 Google 于 2001 年在内部开发,目前已发布到第三个版本(Proto 3)。自发布以来,Protobuf已经成为一种非常流行的序列化解决方案,包括在嵌入式领域中。

实现 protobuf 编码/解码的最著名库是 Nanopb —— 一个开源的 ANSI-C 库,专为嵌入式系统开发,具有极低的 RAM 和代码空间需求。
在 Zephyr 操作系统中,protobuf也被默认作为序列化选项之一

由于nanopb库的使用极为广泛,因此深入了解nanopb的配置选项是有意义的。下面列出了一些常见陷阱及在项目中使用 nanopb 时值得考虑的配置调整。

2. 陷阱 #1:Submessage子消息太多

取决于如何使用 protobuf 和 nanopb,一个显著的性能瓶颈可能是编码速度——即在通过通信链路发送 protobuf 时的编码耗时。
默认情况下,nanopb 为了节省代码空间,会牺牲一定的编码速度。

最容易掉入的陷阱是:

  • 如果proto 消息中包含大量子消息(submessage)。

在编码过程中,nanopb 实际上会对每个子消息编码两次:

  • 第一次用于计算消息大小,
  • 第二次才是真正将其写入 protobuf 缓冲区。

可以很容易地想象,当子消息层级增加时,编码时间会迅速上升。
这就是一个典型的权衡:结构化组织性(使用子消息方便管理数据) vs 编码时间。

要避免这种问题,请确保在嵌入式系统中需要编码/解码的 protobuf 消息中,尽量减少子消息数量。
如果只有一两个(甚至包含少量嵌套消息)一般没问题,但如果层级达到 5、10 层甚至更深,就会严重影响编码效率。

3. 陷阱 #2:流(Stream)抽象

另一个限制是:

  • nanopb 的默认配置是以 流式写入(stream writing) 为核心设计的。
    • 如果确实需要支持从流中读写数据,这当然是个优点;
    • 但如果仅仅需要将 nanopb 输出到内存缓冲区(memory buffer),那么默认配置其实会带来额外开销。

幸运的是,nanopb 的开发者已经意识到这种权衡,并提供了一个编译标志用于回退到“仅使用内存缓冲区”的模式。
这个编译选项叫做 PB_BUFFER_ONLY,它会有条件地移除额外的回调逻辑,从而减少 nanopb 的最终代码体积,并通过减少函数调用数量来加快编码过程。

可以在编译时添加这个宏定义,或者直接在 nanopb 库顶部加入:

#define PB_BUFFER_ONLY

这是一种快速且简单的优化方式,能显著加速基于内存缓冲的 protobuf 编码过程。

4. 避开这些陷阱能带来什么帮助?

4.1 问题背景

需要将一系列体积较小但嵌套层次很深的 protobuf 消息发送到另一个模块。
在每次发送前,这些消息都必须通过 nanopb 进行编码。
原计划是每 100ms 发送一条消息,但在实际运行中发现偶尔会错过时序,于是团队开始调查为什么编码过程这么耗时。

4.2 优化前

在执行该过程前后通过控制 GPIO 的开关来标记时间点,并用逻辑分析仪记录。
逐步分析性能瓶颈后发现,单条消息的编码过程大约耗时 1.5ms,而生成要发送的数据又需要额外的 0.5ms。
下图是通过 PulseView 捕获的一系列 protobuf 编码信号:
在这里插入图片描述

4.3 优化后

通过移除大部分子消息(submessage),并修改 nanopb 的配置为仅使用内存缓冲区(PB_BUFFER_ONLY),编码时间降至 约 0.6ms(同样通过逻辑分析仪验证)。
另一张 PulseView 截图如下:
在这里插入图片描述

将编码时间缩短到原来的 40% 是一个相当显著的改进,尤其是考虑到其中一个改动只是调整 nanopb 的单个配置值!

当然,减少 protobuf 消息中的子消息数量可能比较复杂,特别是当这些消息已被多个模块使用时。
但始终可以弃用(deprecate)旧字段,并逐步迁移到新的结构上。
前提是——得知道 protobuf 的弃用机制是怎么工作的……

5. 陷阱 #3:弃用(Deprecation)支持

当前 Protobuf 3 的实现支持在字段上添加 deprecated 选项,用于标记该字段已弃用。
然而,Google 提供的官方 proto 编译器只有在生成 Java 或 C++ 代码时才会对该选项做出处理。
而作为 ANSI-C 库 的 Nanopb,会完全忽略这个选项。

那么,该如何在 nanopb 中强制弃用某个字段呢?

在 2024 年之前,如果想弃用 nanopb 的字段,可以设置 FT_IGNORE 标志。
这样做会直接移除该字段,使得继续使用该字段时会导致编译错误(hard failure),而非像 Google 建议的那样仅仅发出警告。

在 2024 年初,nanopb 做出了一项改进,增加了 discard_deprecated 选项。当 discard_deprecated 选项 与官方的 deprecated 标签配合使用时,效果等同于 FT_IGNORE

也就是说,虽然目前 deprecated 标签的机制已为未来做好准备,但在现阶段,仍需手动“硬性移除”该字段。

在执行移除时,Google 官方文档 提供了一个非常重要的建议:

如果该字段已无人使用,并且希望防止新用户使用它,请考虑将该字段声明替换为 reserved 语句。

这样可以防止未来的开发者重复使用相同的字段编号,否则可能会导致严重问题

6. 总结

Nanopb 之所以被广泛用于 Zephyr 等系统,是有充分理由的——Nanopb是开源的,稳定可靠的 protobuf 实现。

但如果不了解Nanopb底层机制,很容易遇到各种性能瓶颈或设计陷阱,这些问题不仅会拖慢开发进度,甚至可能影响产品性能与稳定性。

参考资料

[1] 2025年5月博客 Nanopb Traps and How to Avoid Them

内容概要:本文系统研究了电力系统短期负荷预测问题,提出并实现了基于极限学习机(ELM)及其智能优化改进模型的预测方法。研究涵盖标准ELM、白鲸优化算法(BWO)优化ELM和鹭鹰优化算法(IBOA)优化ELM三种模型,重点通过智能优化算法对ELM的输入权重与偏置参数进行全局寻优,有效克服了传统ELM因参数随机初始化导致的不稳定性和泛化能力不足的问题。文章完整呈现了从数据预处理、特征选择、模型构建、参数优化到预测结果对比分析的全流程,利用Matlab编程实现各模型的仿真验证,显著提升了预测精度与模型鲁棒性,为电力系统调度决策提供了可靠的技术支撑。; 适合人群:具备电力系统基础知识、时间序列预测理论及Matlab编程能力的高校研究生、科研机构研究人员以及电力公司从事负荷预测、电网调度与规划工作的技术人员。; 使用场景及目标:①应用于实际电力系统短期负荷预测业务中,提升电网运行调度的精细化与智能化水平;②作为智能优化算法与神经网络融合的经典案例,服务于学术论文撰写、科研项目申报及算法性能对比研究;③应对新能源大规模接入背景下负荷波动加剧的挑战,为构建高精度、强鲁棒性的现代负荷预测体系提供解决方案。; 阅读建议:建议读者结合所提供的Matlab代码进行动手实践,深入理解ELM网络结构与优化算法的集成机制,重点对比分析不同优化策略在收敛速度、预测误差(如MAE、RMSE、MAPE)等方面的性能差异,进而掌握智能优化技术在提升预测模型性能方面的关键作用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值