22 Gobs of data

Gobs of data

24 March 2011

Introduction

To transmit a data structure across a network or to store it in a file, it must be encoded and then decoded again. There are many encodings available, of course: JSONXML, Google‘s protocol buffers, and more. And now there‘s another, provided by Go‘s gobpackage.

Why define a new encoding? It‘s a lot of work and redundant at that. Why not just use one of the existing formats? Well, for one thing, we do! Go has packages supporting all the encodings just mentioned (the protocol buffer package is in a separate repository but it‘s one of the most frequently downloaded). And for many purposes, including communicating with tools and systems written in other languages, they‘re the right choice.

But for a Go-specific environment, such as communicating between two servers written in Go, there‘s an opportunity to build something much easier to use and possibly more efficient.

Gobs work with the language in a way that an externally-defined, language-independent encoding cannot. At the same time, there are lessons to be learned from the existing systems.

Goals

The gob package was designed with a number of goals in mind.

First, and most obvious, it had to be very easy to use. First, because Go has reflection, there is no need for a separate interface definition language or "protocol compiler". The data structure itself is all the package should need to figure out how to encode and decode it. On the other hand, this approach means that gobs will never work as well with other languages, but that‘s OK: gobs are unashamedly Go-centric.

Efficiency is also important. Textual representations, exemplified by XML and JSON, are too slow to put at the center of an efficient communications network. A binary encoding is necessary.

Gob streams must be self-describing. Each gob stream, read from the beginning, contains sufficient information that the entire stream can be parsed by an agent that knows nothing a priori about its contents. This property means that you will always be able to decode a gob stream stored in a file, even long after you‘ve forgotten what data it represents.

There were also some things to learn from our experiences with Google protocol buffers.

Protocol buffer misfeatures

Protocol buffers had a major effect on the design of gobs, but have three features that were deliberately avoided. (Leaving aside the property that protocol buffers aren‘t self-describing: if you don‘t know the data definition used to encode a protocol buffer, you might not be able to parse it.)

First, protocol buffers only work on the data type we call a struct in Go. You can‘t encode an integer or array at the top level, only a struct with fields inside it. That seems a pointless restriction, at least in Go. If all you want to send is an array of integers, why should you have to put it into a struct first?

Next, a protocol buffer definition may specify that fields T.x and T.y are required to be present whenever a value of type T is encoded or decoded. Although such required fields may seem like a good idea, they are costly to implement because the codec must maintain a separate data structure while encoding and decoding, to be able to report when required fields are missing. They‘re also a maintenance problem. Over time, one may want to modify the data definition to remove a required field, but that may cause existing clients of the data to crash. It‘s better not to have them in the encoding at all. (Protocol buffers also have optional fields. But if we don‘t have required fields, all fields are optional and that‘s that. There will be more to say about optional fields a little later.)

The third protocol buffer misfeature is default values. If a protocol buffer omits the value for a "defaulted" field, then the decoded structure behaves as if the field were set to that value. This idea works nicely when you have getter and setter methods to control access to the field, but is harder to handle cleanly when the container is just a plain idiomatic struct. Required fields are also tricky to implement: where does one define the default values, what types do they have (is text UTF-8? uninterpreted bytes? how many bits in a float?) and despite the apparent simplicity, there were a number of complications in their design and implementation for protocol buffers. We decided to leave them out of gobs and fall back to Go‘s trivial but effective defaulting rule: unless you set something otherwise, it has the "zero value" for that type - and it doesn‘t need to be transmitted.

So gobs end up looking like a sort of generalized, simplified protocol buffer. How do they work?

Values

The encoded gob data isn‘t about types like int8 and uint16. Instead, somewhat analogous to constants in Go, its integer values are abstract, sizeless numbers, either signed or unsigned. When you encode an int8, its value is transmitted as an unsized, variable-length integer. When you encode an int64, its value is also transmitted as an unsized, variable-length integer. (Signed and unsigned are treated distinctly, but the same unsized-ness applies to unsigned values too.) If both have the value 7, the bits sent on the wire will be identical. When the receiver decodes that value, it puts it into the receiver‘s variable, which may be of arbitrary integer type. Thus an encoder may send a 7 that came from an int8, but the receiver may store it in an int64. This is fine: the value is an integer and as a long as it fits, everything works. (If it doesn‘t fit, an error results.) This decoupling from the size of the variable gives some flexibility to the encoding: we can expand the type of the integer variable as the software evolves, but still be able to decode old data.

This flexibility also applies to pointers. Before transmission, all pointers are flattened. Values of type int8*int8**int8****int8, etc. are all transmitted as an integer value, which may then be stored in int of any size, or *int, or ******int, etc. Again, this allows for flexibility.

Flexibility also happens because, when decoding a struct, only those fields that are sent by the encoder are stored in the destination. Given the value

type T struct{ X, Y, Z int } // Only exported fields are encoded and decoded.
var t = T{X: 7, Y: 0, Z: 8}

the encoding of t sends only the 7 and 8. Because it‘s zero, the value of Y isn‘t even sent; there‘s no need to send a zero value.

The receiver could instead decode the value into this structure:

type U struct{ X, Y *int8 } // Note: pointers to int8s
var u U

and acquire a value of u with only X set (to the address of an int8 variable set to 7); the Z field is ignored - where would you put it? When decoding structs, fields are matched by name and compatible type, and only fields that exist in both are affected. This simple approach finesses the "optional field" problem: as the type T evolves by adding fields, out of date receivers will still function with the part of the type they recognize. Thus gobs provide the important result of optional fields - extensibility - without any additional mechanism or notation.

From integers we can build all the other types: bytes, strings, arrays, slices, maps, even floats. Floating-point values are represented by their IEEE 754 floating-point bit pattern, stored as an integer, which works fine as long as you know their type, which we always do. By the way, that integer is sent in byte-reversed order because common values of floating-point numbers, such as small integers, have a lot of zeros at the low end that we can avoid transmitting.

One nice feature of gobs that Go makes possible is that they allow you to define your own encoding by having your type satisfy the GobEncoder and GobDecoder interfaces, in a manner analogous to the JSON package‘s Marshaler and Unmarshaler and also to the Stringer interface from package fmt. This facility makes it possible to represent special features, enforce constraints, or hide secrets when you transmit data. See the documentationfor details.

Types on the wire

The first time you send a given type, the gob package includes in the data stream a description of that type. In fact, what happens is that the encoder is used to encode, in the standard gob encoding format, an internal struct that describes the type and gives it a unique number. (Basic types, plus the layout of the type description structure, are predefined by the software for bootstrapping.) After the type is described, it can be referenced by its type number.

Thus when we send our first type T, the gob encoder sends a description of T and tags it with a type number, say 127. All values, including the first, are then prefixed by that number, so a stream of T values looks like:

("define type id" 127, definition of type T)(127, T value)(127, T value), ...

These type numbers make it possible to describe recursive types and send values of those types. Thus gobs can encode types such as trees:

type Node struct {
    Value       int
    Left, Right *Node
}

(It‘s an exercise for the reader to discover how the zero-defaulting rule makes this work, even though gobs don‘t represent pointers.)

With the type information, a gob stream is fully self-describing except for the set of bootstrap types, which is a well-defined starting point.

Compiling a machine

The first time you encode a value of a given type, the gob package builds a little interpreted machine specific to that data type. It uses reflection on the type to construct that machine, but once the machine is built it does not depend on reflection. The machine uses package unsafe and some trickery to convert the data into the encoded bytes at high speed. It could use reflection and avoid unsafe, but would be significantly slower. (A similar high-speed approach is taken by the protocol buffer support for Go, whose design was influenced by the implementation of gobs.) Subsequent values of the same type use the already-compiled machine, so they can be encoded right away.

[Update: As of Go 1.4, package unsafe is no longer use by the gob package, with a modest performance drop.]

Decoding is similar but harder. When you decode a value, the gob package holds a byte slice representing a value of a given encoder-defined type to decode, plus a Go value into which to decode it. The gob package builds a machine for that pair: the gob type sent on the wire crossed with the Go type provided for decoding. Once that decoding machine is built, though, it‘s again a reflectionless engine that uses unsafe methods to get maximum speed.

Use

There‘s a lot going on under the hood, but the result is an efficient, easy-to-use encoding system for transmitting data. Here‘s a complete example showing differing encoded and decoded types. Note how easy it is to send and receive values; all you need to do is present values and variables to the gob package and it does all the work.

package main

import (
    "bytes"
    "encoding/gob"
    "fmt"
    "log"
)

type P struct {
    X, Y, Z int
    Name    string
}

type Q struct {
    X, Y *int32
    Name string
}

func main() {
    // Initialize the encoder and decoder.  Normally enc and dec would be
    // bound to network connections and the encoder and decoder would
    // run in different processes.
    var network bytes.Buffer        // Stand-in for a network connection
    enc := gob.NewEncoder(&network) // Will write to network.
    dec := gob.NewDecoder(&network) // Will read from network.
    // Encode (send) the value.
    err := enc.Encode(P{3, 4, 5, "Pythagoras"})
    if err != nil {
        log.Fatal("encode error:", err)
    }
    // Decode (receive) the value.
    var q Q
    err = dec.Decode(&q)
    if err != nil {
        log.Fatal("decode error:", err)
    }
    fmt.Printf("%q: {%d,%d}\n", q.Name, *q.X, *q.Y)
}

You can compile and run this example code in the Go Playground.

The rpc package builds on gobs to turn this encode/decode automation into transport for method calls across the network. That‘s a subject for another article.

Details

The gob package documentation, especially the file doc.go, expands on many of the details described here and includes a full worked example showing how the encoding represents data. If you are interested in the innards of the gob implementation, that‘s a good place to start.

By Rob Pike

Related articles

原文地址:https://www.cnblogs.com/kaid/p/9698509.html

时间: 2024-08-29 10:52:46

22 Gobs of data的相关文章

关于System.Data.Entity.Core.MetadataException错误

发布ASP.NET MVC到IIS后出现错误: 1 System.Data.Entity.Core.MetadataException: Schema specified is not valid. Errors: 2 SysModel.ssdl(2,2) : error 0152: No Entity Framework provider found for the ADO.NET provider with invariant name 'System.Data.SqlClient'. Ma

ajax异步上传文件之data参数----小哈学js

下载ajaxFileUpload.js(下载网址:http://fileuploadajax.codeplex.com/downloads/get/20976) 修改ajaxFileUpload.js内部程序 1 一.大约在32行 2 createUploadForm: function(id, fileElementId,data) 3 二.大约在47行 4 jQuery(form).appendTo('body');前添加一下代码 5 if (data) { 6 for (var i in

bss、data和rodata区别与联系

有人可能会说,全局内存就是全局变量嘛,有必要专门一章来介绍吗?这么简单的东西,还能玩出花来?我从来没有深究它,不一样写程序吗?关于全局内存这个主题虽然玩不出花来,但确实有些重要,了解这些知识,对于优化程序的时间和空间很有帮助.因为有好几次这样经历,我才决定花一章篇幅来介绍它. 正如大家所知道的,全局变量是放在全局内存中的,但反过来却未必成立.用static修饰的局部变量就是放在放全局内存的,它的作用域是局部的,但生命期是全局的.在有的嵌入式平台中,堆实际上就是一个全局变量,它占用相当大的一块内存

[转] bss段、data段、text段

1.前言 本文主要分编译时和运行时分别对 对data段 bss段 text段 堆 栈作一简要说明 2. 程序编译时概念说明 2.1 bss段 bss段(bss segment)通常是指用来存放程序中未初始化(或初始化为0)的全局变量的一块内存区域. bss是英文Block Started by Symbol的简称. bss段属于静态内存分配. 2.2 data: 数据段(data segment)通常是指用来存放程序中已初始化(非零)的非const的全局变量的一块内存区域. 数据段属于静态内存分

mysql-5.6.22的安装步骤

一.环境与下载地址: 1.系统下载地址: http://mirrors.sohu.com/centos/6.6/isos/x86_64/CentOS-6.6-x86_64-bin-DVD1.iso 2.mysql下载地址: http://mirrors.sohu.com/mysql/?qq-pf-to=pcqq.group http://mirrors.sohu.com/mysql/MySQL-5.6/?qq-pf-to=pcqq.group 推荐使用官方 http://dev.mysql.co

(转)625某电商网站数据库宕机故障解决实录(下)

1.4开始进行故障恢复***** 1.重新初始化建库 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 [[email protected] data]# mkdir mysql [[email protected] data]# chown -R mysql.mysql mysql [ro[email protected] data]# /install/mysql/sc

Ajax请求传递参数遇到的问题

想写个同类型的,代码未测. 什么是WebAPI?我的理解是WebAPI+JQuery(前端)基本上能完成Web MVC的功能,即:这么理解吧,WebAPI相当于Web MVC的后台部分. 接下来直接上例子吧,都是我在学习过程中遇到或者发现的一些问题.  一.创建WebAPI项目 (这个环节不是本章重点) 二.传递参数遇到的问题 后台实体类(Person): 1 namespace WebApi.Models 2 { 3 public class Person 4 { 5 6 public int

rsync远程数据备份配置之再次总结

一.实验环境 主机名  网卡ip  默认网关 用途 nfs-server 10.0.0.11 10.0.0.254 rsync服务器端 web-client01 10.0.0.12 10.0.0.254 rsync客服端 web-client02 10.0.0.13 10.0.0.254 rsync客服端 二.实验步骤 1.什么是rsync?rsync是一款开源的,快速的,多功能的可实现全量及增量的数据备份同步的优秀工具,适用于多种操作系统之上.2.rsync特性1)支持拷贝链接文件特殊文件2)

jquery ajax跨域的完美解决方法(jsonp方式)

ajax跨域请求的问题,JQuery对于Ajax的跨域请求有两类解决方案,不过都是只支持get方式,接下来为大家详细介绍下客户端JQuery.ajax的调用代码 今天在项目中需要做远程数据加载并渲染页面,直到开发阶段才意识到ajax跨域请求的问题,隐约记得Jquery有提过一个ajax跨域请求的解决方式,于是即刻翻出Jquery的API出来研究,发 JQuery对于Ajax的跨域请求有两类解决方案,不过都是只支持get方式.分别是JQuery的 jquery.ajax jsonp格式和jquer