bigywater.tech
Go · 113

encoding/gob:类型信息只发一次的二进制编码

gob 是 Go 自己的二进制编码,第一次传输一个类型时会把类型描述一起发过去,后面的消息只带数据。这篇用字节数和耗时验证这个设计:什么时候又小又快,什么时候(每次新建 Encoder/Decoder)反而比 JSON 慢得多;并看它怎么处理字段增减、接口字段。

51 → 11 字节
同一个 Encoder 连续编码同一个类型,第一条与之后每条的大小
347 vs 2163 ns
gob 编码一个结构体:复用 Encoder 与每次新建 Encoder
619 vs 9360 ns
gob 解码:一个 Decoder 连续解 1000 条的平均与每次新建 Decoder

类型信息只发一次

实测 · 本机执行输出
  同一个 Encoder 连续 Encode 4 个 Point{X,Y,Name}:每条消息的字节数 = [51 11 11 11](第一条含类型定义)
  每次新建 Encoder:每条消息的字节数 = [51 51 51 51](类型定义每次都重发)
  同一个值的 JSON:24 字节({"X":1,"Y":2,"Name":"p"})
  全是零值的 Point{} 用新 Encoder 编码:44 字节(零值字段不编码)
  第 2 条消息(同一个 Encoder)的十六进制:0aff800104010401017000

第一条消息 51 字节,其中大部分是类型定义;之后每条只有 11 字节(JSON 是 24 字节)。每次新建 Encoder 就每条都是 51 字节。所以 gob 适合“一个连接上持续传一串同类型的值”,不适合每条消息单独编码(比如存进数据库的一个字段)。

兼容性

实测 · 本机执行输出
  V1{A:7,B:"hi"} 解码进字段顺序不同、多了 C 的 V2:{B:hi C:0 A:7},err=<nil>
  解码进 A 变成 string 的 V3:{A:},err=gob: wrong type (string) for received field V1.A
  解码进一个字段名完全不同的结构体:{Z:0},err=gob: type mismatch: no fields matched compiling decoder for Empty

接收方按字段名匹配,字段顺序不同、接收方多出来的字段(保持零值)都没问题;字段类型变了(int → string)或者一个字段名都对不上才报错。不过接口字段必须 gob.Register 具体类型(文档说两端都要注册;本实验在同一个进程里,没有把两端分开测)。

接口字段

实测 · 本机执行输出
  没有 Register 就编码含接口字段的结构体:err=gob: type not registered for interface: main.Dog
  gob.Register(Dog{}) 之后:编码 err=<nil>,解码 err=<nil>,得到 main.Holder{A:main.Dog{Name:"旺财"}}

没注册就编码,报 type not registered for interface: main.Dog;gob.Register(Dog{}) 之后能正常往返。

速度

实测 · 本机执行输出
  Big 结构体:JSON 118 字节;gob 新 Encoder 的单条 190 字节;同一个 Encoder 的第 2 条 62 字节
  编码:gob 每次新建 Encoder 2163 ns/次、27 次分配;gob 复用 Encoder 347 ns/次、5 次分配;JSON 675 ns/次、7 次分配
  解码:gob(每次新建 Decoder)9360 ns/次、200 次分配;JSON 1043 ns/次、8 次分配
  解码:一个 Decoder 连续解 1000 条消息,平均每条 619 ns、14 次分配(含第一条编译解码器的成本)

复用 Encoder 编码一个结构体 347 ns,比 JSON 的 675 ns 快,只有 5 次分配;每次新建 Encoder 要 2163 ns、27 次分配,比 JSON 慢。解码的差距更大:每次新建 Decoder 9360 ns、200 次分配,是 JSON(1043 ns)的 9 倍;一个 Decoder 连续解 1000 条平均 619 ns。

为什么

实测 · 本机执行输出
  The implementation compiles a custom codec for each data type in the stream and is most efficient when a single Encoder is used to transmit a stream of values, amortizing the cost of compilation.

文档说得很直接:它为流里每个类型编译一个定制编解码器,单个 Encoder 传一串值时摊薄了编译成本。

分步演示

encoding/gob
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1(brew);用 go run / go build / go test 直接编译运行,需要第三方库时经代理从 proxy.golang.org 下载;计时用 time.Now / testing.B,数字每次运行会有小幅抖动。大小来自 bytes.Buffer 的长度;速度用 testing.Benchmark;Big 结构体含字符串、切片、map;“一个 Decoder 连续解 1000 条”的平均已包含第一条编译解码器的成本;读过的文档:go doc encoding/gob 开头的一句话
    • 没有验证:gob 的线格式细节(只展示了一条消息的十六进制)、与 protobuf 等格式的对比、很大的切片/嵌套结构下的表现、跨 Go 版本的兼容性、net/rpc 里的实际用法
    京ICP备2026067502号-1京公网安备11010502063487号