性能表现

本文将介绍 bitproto 的编解码性能表现和优化机制。

性能压测

对 bitproto 的编解码的性能压测结果显示,它跑的非常快。

Unix 系统

在类 unix 系统 (mac, ubuntu etc.) 上,测试 100 字节大小的消息, 一次编码调用或者解码调用占用:

  • C 语言中 < 2μs

  • Go 语言中 <10μs

  • Python 语言中 < 1ms

你可以在 github 上查看 unix 系统上详细的压测结果

嵌入式

我在一个 stm32 开发板 (arm cortex-m3 72MHz cpu) 上, 对 100 字节大小的消息进行了压测测试, 一次编码调用或者解码调用占用 120~140 μs 左右的时间,并且可以通过 优化模式 来降低到 15 μs 左右。

你可以在 github 上查看 stm32 上详细的压测结果

优化模式

对于大多数场景,bitproto 的性能应该可以满足需求。但是如果你对此不满意,仍然有一种方法可以尝试,即 bitproto 中所谓的 "优化模式" 。通过给 bitproto 的编译器添加一个 选项 -O , 可以开启优化模式:

$ bitproto c example.bitproto -O

这样,bitproto 会生成优化模式下的代码。

优化模式背后的机制是,在代码生成阶段直接生成直白的编解码代码语句。我们知道,bitproto 中所有的类型都是定长的,因此在代码生成阶段是完全可以知道如何对数据进行编解码的。在代码生成阶段,bitproto 会遍历消息的所有字段然后生成比特拷贝的语句。

备注

优化模式不支持和 扩展性消息 一起工作。因为扩展性消息的解码需要依赖动态的计算。

以 C 语言举例,优化模式下生成的代码是这个样子的:

int EncodeDrone(struct Drone *m, unsigned char *s) {
    s[0] |= (((unsigned char *)&((*m).position.latitude))[0] << 3) & 248;
    s[1] = (((unsigned char *)&((*m).position.latitude))[0] >> 5) & 7;
    ...
}

int DecodeDrone(struct Drone *m, unsigned char *s) {
    ((unsigned char *)&((*m).position.latitude))[0] = (s[0] >> 3) & 31;
    ((unsigned char *)&((*m).position.latitude))[0] |= (s[1] << 5) & 224;
    ...
}

上面的生成代码中,没有循环语句、没有 if-else 语句,所有的语句都是直白的比特操作。通过这种方式,bitproto 的优化模式可以给我编解码效率上最大化的提升。

对于通信双方中,一方使用无优化模式 (标准模式),一方使用优化模式,这种情况也是没有问题的。优化模式只改变了编码函数和解码函数的编写方式,并没有改变数据的交换格式。

事实上,使用优化模式也是一种权衡。在这个模式下,我们不得不放弃 扩展性功能 ,这样其实对协议的兼容性设计是不友好的。优化模式是为了性能敏感的场景设计的,比如低功耗的嵌入式板,计算密集的微控制器。在以下场景下,推荐使用优化模式:

  • 性能敏感的场景,100μs10μs 完全不是一回事。

  • 通信相关方的固件升级总是一起进行的,这样向前兼容性就不那么重要了。

  • 固件的升级并不会频繁,甚至很长时间一次。

特殊的,对于那种固件升级必须一部分一部分进行,没办法一次性升级的情况,比如典型的 CS架构 ,我还是推荐坚持使用标准模式,而不是优化模式。

目前,bitproto 的优化模式只支持 C 语言和 Go 语言,目前还不支持 Python 。

优化模式所带来的另一个好处是不必在使用支持库了。因为 bitproto 的编译器在优化模式下已经生成了最终的编解码语句,因此 bitproto 的支持库就不需要了。这些支持库最初的设计是为了支持标准模式下的协议扩展性能力。

更小的代码量

嵌入式固件可能受限于编译后程序占用空间的大小。 bitproto 提供了一个编译器选项 -F 来筛选需要生成编解码函数的消息名字列表:

$ bitproto example.bitproto -O -F "Packet"

上面的命令中, bitproto 会只生成消息 Packet 的编码函数和解码函数,其他消息的编码函数和解码函数则会跳过,不再生成。

-F 选项之所以是有用处的,是因为在很多情况下,我们在通信过程中只交换一个"顶级的"消息。这个选项也可以用来筛选多个消息名字,使用逗号分隔即可:

$ bitproto example.bitproto -O -F "PacketA,PacketB"

最后需要说明的是,-F 选项只可以用在优化模式下,也就是所只能和 -O 选项搭配使用。