今天我们开源Jingo,这是一个用于golang的快速JSON库。https://github.com/bet365/jingo
go标准库json编码器实现的接口非常好 - 你使用标签装饰你的结构然后将它们传递给Marshal,就像这样......
encoding/json 的用法示例:
import "encoding/json"
type MyPayload struct {
Name string `json:"name"`
Age int `json:"age"`
ID int `json:"-"`
}
func main(){
p := MyPayload{
Name: "Mr Payload",
Age: 33,
}
serialized, err := json.Marshal(&payload)
// serialized = {"name":"Mr Payload","age":33}
}
在大多数情况下,这可能是您所需要的,但在高性能方案中,此默认实现可能会成为瓶颈。这导致了由Go社区驱动的相当多的其他JSON实现的创建,每个实现都以不同程度的成功和实现成本解决了性能问题。一般来说,库越快,您的结构越适合支持库就越困难。
理想情况下,我们想要的是与标准库相同(或类似)的界面,但是像gojay这样的其他库能够实现的所有性能优势。
所以Jingo就是我们最终得到的,而这就是它的相似之处
Jingo的示例用法
import "github.com/bet365/jingo"
type MyPayload struct {
Name string `json:"name"`
Age int `json:"age"`
ID int
}
var enc = jingo.NewStructEncoder(MyPayload{})
func main() {
p := MyPayload{
Name: "Mr Payload",
Age: 33,
}
buf := jingo.NewBufferFromPool()
enc.Marshal(&p, buf) // buf = {"name":"Mr Payload","age":33}
}
与前一个示例的主要区别在于我们使用我们想要编码的类型的空白结构创建了一个新的编码器实例,它就是我们称之为Marshal的实例。
下一个区别是我们正在使用我们自己的轻量级缓冲区类型来强制使用缓冲区,该缓冲区类型内置了池。
当我们使用缓冲区的内容时,我们调用buf.ReturnToPool()它以便可以重复使用,这大大减少了我们的内存分配总体。
它是如何工作的?
当您创建编码器的实例时,它会递归地生成一个指令集,该指令集定义了如何迭代编码结构。
这使它能够提供清晰的API,但具有与构建时优化编码器相同的优点。
它几乎完全能够在这个编译阶段完成所有类型的断言和反射活动,然后unsafe在指令集执行(Marshal调用)期间充分利用包来使读取和写入非常快。
作为指令集编译的一部分,它还生成静态元数据,即字段名称,括号,大括号等。然后根据需要将这些元数据分块。
使用上面示例中的数据,并稍微简化调用堆栈,我们最终得到的是一组轻量级指令,看起来像这样
write_buf `{"name":"`
write_buf_from_ptr `Mr Payload`
write_buf `","age":`
write_buf_from_ptr `33`
write_buf `}`
正如我们已经提到的,所有类型信息都被推断为生成指令集的一部分,因此每条指令都知道要读取的确切结构偏移量和大小。
您现在可以使用的编码器是jingo.StructEncoder和jingo.SliceEncoder。这些覆盖了我们自己的绝大多数用例,但很快就会出现地图编码器。
它的表现如何?
非常好,因为它发生了。看一看下面的附图中,它们与生成的gojay Perf数据,SmallPayload和LargePayload分别。
这些结果可能更加明显,具体取决于结构的形状 - 这些结果基于具有大量字符串数据的结构:
Jingo实际上比我们现在能找到的任何东西都快,所以我们开源并与更广泛的社区分享似乎是一个很好的候选人。
社区贡献
说到,Jingo欢迎捐款。请参阅https://github.com/bet365/jingo以获取一系列指导原则。
未来
我们有兴趣看看我们能够在多大程度上采用构成编码器基础的一系列构思。因此,虽然解码器在卡上,但可能有其他应用程序和编码格式可以从这种方法中受益。
与此同时,如果您选择这样做,我们很想听听您使用Jingo的经历!
閱讀更多 guorenweitianxia 的文章