注册

ASN 和 PB 的编码效率比较


作者:王纯业 ,转自http://blog.easemob.com/

### 第一个例子
http://martin.kleppmann.com/2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html 是一个很好的比较例子。
我类似的做了一个 ASN1 的结构
```
Person DEFINITIONS AUTOMATIC TAGS ::=
BEGIN
  Person ::= SEQUENCE {
    username PrintableString,
    favouritenumber INTEGER,
    interests SEQUENCE OF PrintableString
  }
END
```
用下面的方法编译
```
erlc -I. -bper Person.asn
erl
> c("Person").
{ok,'Person'}
> {ok, B} = 'Person':encode('Person', #'Person'{username = "Martin", favouritenumber = 1337, interests = ["daydreaming", "hacking"]}).
{ok,<<6,77,97,114,116,105,110,2,5,57,2,11,100,97,121,100,
      114,101,97,109,105,110,103,7,104,97,99,...>>}
> byte_size(B).
31
```
这个例子里面,ASN1 用了 31 bytes ,protobuf 用了 33 bytes, Avro 用了
32 bytes。 这不是一个公平的比较,对于大量使用小数据结构的时候,例如,
enum command type 之类的,ASN1 可以节省更多的 bytes 。
### 第二个例子
这是 protobuf 的定义。
```
package dummy;
message S {
  optional int32 a =1;
  optional bool b =2;
  optional int32 c =3;
  optional D   d =4;
}
message D {
  optional bool d1 = 1;
  optional bool d2 = 2;
}
```
用 erlang 编译 参考 [https://github.com/tomas-abrahamsson/gpb]()
```
> deps/gpb/bin/protoc-erl -I. -o-erl src -o-hrl include s1.proto
> erl -sname a@localhost
(a@localhost)1> R = #'S'{a=1,b=true,c=2, d=#'D'{d1 = true, d2 = true} }.
#'S'{a =1,b = true,c = 2,d = #'D'{d1 = true,d2 = true}}
(sync@localhost)2> s1:encode_msg(R).
<<8,1,16,1,24,2,34,4,8,1,16,1>>
(a@localhost)14> byte_size(s1:encode_msg(R)).
12
```
可以看到 protobuf 用了 12 个字节。
ASN1 的例子,使用  PER 编码方式。
```
Dummy DEFINITIONS AUTOMATIC TAGS ::=
BEGIN
  Dummy ::= SEQUENCE {
   a INTEGER (0..7),
   b BOOLEAN,
   c INTEGER (0..3),
   d SEQUENCE {
      d1 BOOLEAN,
      d2 BOOLEAN }}
END
```
```
> erlc -I. -bper Dummy.asn
> erl
(a@localhost)1> 'Dummy':encode('Dummy', #'Dummy'{ a = 1, b = true, c = 2, d = #'Dummy_d'{d1= true, d2 = true }}).
{ok,<<";">>}
```
protobuf 用了  12 个字节, ASN1 用了 1 个字节。同样,这也不是一个公平的比较。
很难做出公平的比较。但是可以说在大多数情况下 ASNPER 的编码是更加节省带宽的。
### 为什么 ASN1 PER 的编码效率比 PB 的高
1. ASN1 PER 是面向 bit 的编码方式,PB 是面向字节的编码方式。
2. PB 中 message 都是可以扩展的,ASN1 中只有使用  `...` 关键字的类型,才是可以扩展的。
3. PB 中的整数很简单,都是可以扩展到 64 位,ASN1 中有更加灵活(复杂) 的整数扩展方式。
2. PB 中的 `required`, `optional`, `oneof`, 和 `extensions` 的特性,对编码没有影响。例如,就算是 `required` 的字段,编码的时候,也是需要 tag 。
3. ASN1 PER 对很多关键字都是敏感的。例如
   1. `required` 的字段不会添加表明类型的 tag
   2. `required` 的字段按顺序编码。
4. tag 在 PER 中不做编码。
5. by default, every message is extensible in PB. Instread, ASN1 extensibility should be explicitly specified.
4. PB 中支持的整数类型不支持 subtype, 而 ASN1 PER 中的整数支持 subtype , 可以实现高效编码。
### 使用 ASN1 的优点
1. 编码紧凑,节省带宽。这是为什么几乎所有的 2G/3G/4G/5G 的无线通信协议都使用 ASN1 的原因之一。
2. ASN1 久经考验,asn1c 的项目已经十多年了,依然活跃开发。 Erlang 因为是通信公司创造的,语言内嵌 ASN 的支持。 Erlang 没有默认支持 PB 需要使用第三方开发库。
3. ASN1 支持 XER (XML) ,可以方便的调试。
4. wireshark 本身对 ASN1 的支持很好。
### 使用 ASN1 PER 的风险
1. ASN1 本身很复杂。ASN1 的学习成本高
2. PER 编码很复杂。可以用700行 C 代码实现 PB 的编解码,但实现 PER 编码不行。
3. ASN1 对语言的支持不多,似乎只有  C/Erlang 有比较好的使用。由于历史原因,通信领域几乎没有其他语言可供选择。
 

0 个评论

要回复文章请先登录注册