前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >proto vs gzip proto in golang

proto vs gzip proto in golang

作者头像
天地一小儒
发布2022-12-28 14:46:07
6500
发布2022-12-28 14:46:07
举报
文章被收录于专栏:信且诚心之动信且诚心之动

我们知道proto是基于二进制编码的,比json格式的编码要节省大量空间,那么,如果对于proto编码后的结果再进行gzip压缩,是否能产生更多空间的节省呢?gzip压缩是否具有幂等性呢?本文讨论了在golang中对这两个问题的探索和研究

gzip压缩proto编码结果

准备条件

proto定义:

// 共识规则
message ReviewRule {
  string id = 1;
  string name = 2;
  string desc = 3;
  string operator = 4;
  string tx_id = 5;
  repeated string participants = 6;
  ReviewType review_type = 7;
  google.protobuf.Struct vote = 8;
}

// 共识投票类型
enum ReviewType {
  // 全体成员投票通过
  All = 0;
  // 任意成员投票通过
  OneOf = 1;
  // 指定成员投票通过
  Designation = 2;
  // 比例成员投票通过,如30%, 50%, 70%
  Scale = 3;
}

在进行这个测试时,主要研究属性对象是ReviewRule的6,7,8,会根据长度大小生成固定长度的uuid字符串(随机),然后对ReviewRule做proto编码和proto编码后的gzip压缩。同时比对gzip解压缩后和原proto编码的字节长度是否一致,确保压缩和解压缩是对proto编码的结果无影响的。

结果比对

单位:字节Byte

随机长度

proto编码后

gzip写入

gzip压缩

gzip读取

gzip解压缩

gzip节省空间比率(%)

0

413

413

325

413

413

21.31

1

499

499

388

499

499

22.24

2

583

583

439

583

583

24.70

20

2099

2099

1240

2099

2099

40.92

200

17219

17219

8670

17219

17219

49.65

2000

168423

168423

82245

168423

168423

51.17

20000

1680423

1680423

816971

1680423

1680423

51.38

备注:gzip写入和gzip读取是为了保证在gzip处理过程中没有发生数据丢失或复写。

从上述表格中可以看到,gzip压缩后确实能在proto编码后再次降低使用的空间大小的,甚至数据量越大,压缩比越高,1.6G的数据大约可以降到800M不到,超过了50%。且解压缩后,数据大小仍然保持一致。

那么压缩的结果是否每次都能保持完全一致呢?

gzip压缩的幂等性

同样,在这个测试时,也是生成了长度为20000的随机参数ReviewRule。

结果比对

压缩次数

压缩结果base64编码结果数

压缩结果长度结果数

2

1

1

5

1

1

10

1

1

20

1

1

50

1

1

100

1

1

可以看到,对于相同的结果,压缩结果大小和base64编码后的结果不随压缩次数的增加而发生变化,因此可以推断gzip压缩是具有幂等性的,即压缩的结果每次都能保持完全一致。

附录

go版本:1.17.9 完整测试文件:

package v1

import (
    "bytes"
    "compress/gzip"
    "context"
    "encoding/base64"
    "fmt"
    "io"
    "testing"

    "github.com/google/uuid"
    "github.com/pkg/errors"
    "github.com/stretchr/testify/assert"
    "golang.org/x/sync/errgroup"
    "google.golang.org/protobuf/proto"
    "google.golang.org/protobuf/types/known/structpb"
)

func newTestReviewRule() *ReviewRule {
    rr := new(ReviewRule)
    rr.Id = uuid.New().String()
    rr.Name = "ReviewRule"
    rr.Desc = `// Code generated by protoc-gen-go. DO NOT EDIT.
    // versions:
    //  protoc-gen-go v1.28.0
    //  protoc        v3.9.0
    // source: pkg/contracts/fabric/review/v1/review.proto`
    rr.Operator = "x509::CN=hlp,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=AR"
    rr.TxId = uuid.New().String()
    return rr
}

func fillRandomParams(rr *ReviewRule, length int) error {
    rr.Participants = genStringSlice(length)
    rr.ReviewType = ReviewType(length % (len(ReviewType_name) - 1))

    vote, err := structpb.NewStruct(map[string]interface{}{
        "vote": genInterfaceSlice(length),
    })
    if err != nil {
        return errors.Wrap(err, "structpb.NewStruct")
    }
    rr.Vote = vote

    return nil
}

func Test_proto_gzip(t *testing.T) {
    rr := newTestReviewRule()
    tests := []struct {
        name   string
        length int
    }{
        {"0", 0},
        {"1", 1},
        {"2", 2},
        {"20", 20},
        {"200", 200},
        {"2000", 2000},
        {"20000", 20000},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            assert.Nil(t, fillRandomParams(rr, tt.length))

            protoBytes, err := proto.Marshal(rr)
            assert.Nil(t, err)
            var buf bytes.Buffer
            zw := gzip.NewWriter(&buf)

            i, err := zw.Write(protoBytes)
            assert.Nil(t, err)
            assert.Nil(t, zw.Close())

            fmt.Printf("name[%s]: proto[%d], gzip wi[%d], gzip[%d], ratio[%.2f], ", tt.name, len(protoBytes), i, buf.Len(), float64(i-buf.Len())/float64(i)*100)
            zr, err := gzip.NewReader(&buf)
            assert.Nil(t, err)
            defer zr.Close()

            var buf_ bytes.Buffer
            i_, err := io.Copy(&buf_, zr)
            assert.Nil(t, err)

            fmt.Printf("gzip ri[%d], read[%d]\n", i_, buf_.Len())
        })
    }
}

func genStringSlice(length int) []string {
    s := make([]string, length)
    for i := 0; i < length; i++ {
        s = append(s, genRandomString())
    }

    return s
}

func genInterfaceSlice(length int) []interface{} {
    s := make([]interface{}, length)
    for i := 0; i < length; i++ {
        s = append(s, genRandomString())
    }

    return s
}

func genRandomString() string {
    return uuid.New().String()
}

func Test_idempotent_gzip(t *testing.T) {
    rr := newTestReviewRule()
    assert.Nil(t, fillRandomParams(rr, 20000))
    protoBytes, err := proto.Marshal(rr)
    assert.Nil(t, err)
    tests := []struct {
        name      string
        frequency int
    }{
        {"2", 2},
        {"5", 5},
        {"10", 10},
        {"20", 20},
        {"50", 50},
        {"100", 100},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            stream := make(chan []byte)
            eg, egCtx := errgroup.WithContext(context.Background())
            for i := 0; i < tt.frequency; i++ {
                eg.Go(func() error {
                    var buf bytes.Buffer
                    zw := gzip.NewWriter(&buf)
                    _, err = zw.Write(protoBytes)
                    if err != nil {
                        return errors.Wrap(err, "zw.Write")
                    }

                    if err = zw.Close(); err != nil {
                        return errors.Wrap(err, "zw.Close")
                    }

                    select {
                    case <-egCtx.Done():
                        return errors.New("eg context done")
                    case stream <- buf.Bytes():
                    }

                    return nil
                })
            }

            go func() {
                eg.Wait()
                close(stream)
            }()

            base64Set := make(map[string]interface{})
            lenSet := make(map[int]interface{})
            for gzipBytes := range stream {
                base64Set[base64.StdEncoding.EncodeToString(gzipBytes)] = struct{}{}
                lenSet[len(gzipBytes)] = struct{}{}
            }
            assert.Nil(t, eg.Wait())
            assert.Len(t, base64Set, 1)
            assert.Len(t, lenSet, 1)
        })
    }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-06-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • gzip压缩proto编码结果
    • 准备条件
      • 结果比对
      • gzip压缩的幂等性
        • 结果比对
        • 附录
        相关产品与服务
        文件存储
        文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档