优化 Go 语言数据打包:性能基准测试与分析

场景:在局域网内,需要将多个机器网卡上抓到的数据包同步到一个机器上。

原有方案:tcpdump -w 写入文件,然后定时调用 rsync 进行同步。

改造方案:使用 Go 重写这个抓包逻辑及同步逻辑,直接将抓到的包通过网络发送至服务端,由服务端写入,这样就减少了一次落盘的操作。

构造一个 pcap 文件很简单,需要写入一个 pcap文件头,后面每一条数据增加一个元数据进行描述。

使用 pcapgo 即可实现这个功能,p.buffer[:ci.CaptureLength] 为抓包的数据。

ci := gopacket.CaptureInfo{
CaptureLength: int(n),
Length: int(n),
Timestamp: time.Now(),
}
if ci.CaptureLength > len(p.buffer) {
ci.CaptureLength = len(p.buffer)
}
w.WritePacket(ci, p.buffer[:ci.CaptureLength])

为了通过区分是哪个机器过来的数据包需要增加一个 Id,算上元数据和原始数据包,表达结构如下

// from github.com/google/gopacket
type CaptureInfo struct {
// Timestamp is the time the packet was captured, if that is known.
Timestamp time.Time `json:"ts" msgpack:"ts"`
// CaptureLength is the total number of bytes read off of the wire.
CaptureLength int `json:"cap_len" msgpack:"cap_len"`
// Length is the size of the original packet. Should always be >=
// CaptureLength.
Length int `json:"len" msgpack:"len"`
// InterfaceIndex
InterfaceIndex int `json:"iface_idx" msgpack:"iface_idx"`
} type CapturePacket struct {
CaptureInfo
Id uint32 `json:"id" msgpack:"id"`
Data []byte `json:"data" msgpack:"data"`
}

有一个细节待敲定,抓到的包使用什么结构发送至服务端?json/msgpack/自定义格式?

json/msgpack 都有对应的规范,通用性强,不容易出 BUG,性能会差一点。

自定义格式相比 json/msgpack 而言,可以去掉不必要的字段,连 key 都可以不用在序列化中出现,并且可以通过一些优化减少内存的分配,缓解gc压力。

自定义二进制协议优化思路如下

  1. CaptureInfo/Id 字段直接固定N个字节表示,对于 CaptureLength/Length 可以直接使用 2 个字节来表达,Id 如果数量很少使用 1 个字节来表达都可以
  2. 内存复用
    1. Encode 逻辑内部不分配内存,这样直接写入外部的 buffer,如果外部 buffer 是同步操作的话,整个逻辑 0 内存分配
    2. Decode 内部不分配内存,只解析元数据和复制 Data 切片,如果外部是同步操作,同样整个过程 0 内存分配
    3. 如果是异步操作,那么在调用 Encode/Decode 的地方对 Data 进行复制,这里可以使用 sync.Pool 进行优化,使用四个 sync.Pool 分别分配 128/1024/8192/65536 中数据

sync.Pool 的优化点有两个

  • 异步操作下每个 Packet.Data 都需要有自己的空间,不能进行复用,使用 sync.Pool 来构造属于 Packet 的空间
  • 元数据序列化固定字节长度的 buffer,使用 make 或者数组都会触发 gc
func acquirePacketBuf(n int) ([]byte, func()) {
var (
buf []byte
putfn func()
)
if n <= CapturePacketMetaLen+128 {
smallBuf := smallBufPool.Get().(*[CapturePacketMetaLen + 128]byte)
buf = smallBuf[:0]
putfn = func() { smallBufPool.Put(smallBuf) }
} else if n <= CapturePacketMetaLen+1024 {
midBuf := midBufPool.Get().(*[CapturePacketMetaLen + 1024]byte)
buf = midBuf[:0]
putfn = func() { midBufPool.Put(midBuf) }
} else if n <= CapturePacketMetaLen+8192 {
largeBuf := largeBufPool.Get().(*[CapturePacketMetaLen + 8192]byte)
buf = largeBuf[:0]
putfn = func() { largeBufPool.Put(largeBuf) }
} else {
xlargeBuf := xlargeBufPool.Get().(*[CapturePacketMetaLen + 65536]byte)
buf = xlargeBuf[:0]
putfn = func() { xlargeBufPool.Put(xlargeBuf) }
}
return buf, putfn
} func (binaryPack) EncodeTo(p *CapturePacket, w io.Writer) (int, error) {
buf := metaBufPool.Get().(*[CapturePacketMetaLen]byte)
defer metaBufPool.Put(buf) binary.BigEndian.PutUint64(buf[0:], uint64(p.Timestamp.UnixMicro()))
...
return nm + nd, err
}

数据包构造大小(By 通义千问)

方法 原始数据长度 (字节) 编码后数据长度 (字节) 变化字节数 (字节)
Binary Pack 72 94 +22
Binary Pack 1024 1046 +22
Binary Pack 16384 16406 +22
MsgPack 72 150 +78
MsgPack 1024 1103 +79
MsgPack 16384 16463 +79
Json Pack 72 191 +119
Json Pack 1024 1467 +443
Json Pack 16384 21949 +5565
Json Compress Pack 72 195 +123
Json Compress Pack 1024 1114 +90
Json Compress Pack 16384 15504 -120

分析

  • Binary Pack

    • 对于较小的数据(72字节),编码后增加了22字节。
    • 对于较大的数据(16384字节),编码后增加了22字节。
    • 总体来看,Binary Pack的编码效率较高,增加的字节数相对较少。
  • MsgPack

    • 对于较小的数据(72字节),编码后增加了78字节。
    • 对于较大的数据(16384字节),编码后增加了79字节。
    • MsgPack的编码效率在小数据量时不如Binary Pack,但在大数据量时仍然保持较高的效率。
  • Json Pack

    • 对于较小的数据(72字节),编码后增加了119字节。
    • 对于较大的数据(16384字节),编码后增加了5565字节。
    • Json Pack的编码效率较低,特别是对于大数据量,增加的字节数较多。
  • Json Compress Pack

    • 对于较小的数据(72字节),编码后增加了123字节。
    • 对于较大的数据(16384字节),编码后增加了120字节。
    • Json Compress Pack在小数据量时增加的字节数较多,但在大数据量时增加的字节数较少,表明压缩效果较好。

通过这个表格,你可以更直观地看到不同数据打包方法在不同数据量下的表现。希望这对你有帮助!

benchmark

json

可以看到使用 buffer 进行复用提升比较明显,主要还是减少内存分配带来的提升。

BenchmarkJsonPack/encode#72-20                    17315143             647.1 ns/op             320 B/op          3 allocs/op
BenchmarkJsonPack/encode#1024-20 4616841 2835 ns/op 1666 B/op 3 allocs/op
BenchmarkJsonPack/encode#16384-20 365313 34289 ns/op 24754 B/op 3 allocs/op
BenchmarkJsonPack/encode_with_buf#72-20 24820188 447.4 ns/op 128 B/op 2 allocs/op
BenchmarkJsonPack/encode_with_buf#1024-20 13139395 910.6 ns/op 128 B/op 2 allocs/op
BenchmarkJsonPack/encode_with_buf#16384-20 1414260 8472 ns/op 128 B/op 2 allocs/op
BenchmarkJsonPack/decode#72-20 8699952 1364 ns/op 304 B/op 8 allocs/op
BenchmarkJsonPack/decode#1024-20 2103712 5605 ns/op 1384 B/op 8 allocs/op
BenchmarkJsonPack/decode#16384-20 159140 73101 ns/op 18664 B/op 8 allocs/op

msgpack

同样看到使用 buffer 进行复用的提升,和 json 的分水岭大概在 1024 字节左右,超过这个大小 msgpack 速度快很多,并且在解析的时候内存占用不会随数据进行增长。

BenchmarkMsgPack/encode#72-20                     10466427              1199 ns/op             688 B/op          8 allocs/op
BenchmarkMsgPack/encode#1024-20 6599528 2132 ns/op 1585 B/op 8 allocs/op
BenchmarkMsgPack/encode#16384-20 1478127 8806 ns/op 18879 B/op 8 allocs/op
BenchmarkMsgPack/encode_with_buf#72-20 26677507 388.2 ns/op 192 B/op 4 allocs/op
BenchmarkMsgPack/encode_with_buf#1024-20 31426809 400.2 ns/op 192 B/op 4 allocs/op
BenchmarkMsgPack/encode_with_buf#16384-20 22588560 494.5 ns/op 192 B/op 4 allocs/op
BenchmarkMsgPack/decode#72-20 19894509 654.2 ns/op 280 B/op 10 allocs/op
BenchmarkMsgPack/decode#1024-20 18211321 664.0 ns/op 280 B/op 10 allocs/op
BenchmarkMsgPack/decode#16384-20 13755824 769.1 ns/op 280 B/op 10 allocs/op

压缩的效果

在内网的情况下,带宽不是问题,这个压测结果直接被 Pass

BenchmarkJsonCompressPack/encode#72-20               19934            709224 ns/op         1208429 B/op         26 allocs/op
BenchmarkJsonCompressPack/encode#1024-20 17577 766349 ns/op 1212782 B/op 26 allocs/op
BenchmarkJsonCompressPack/encode#16384-20 11757 860371 ns/op 1253975 B/op 25 allocs/op
BenchmarkJsonCompressPack/decode#72-20 490164 28972 ns/op 42048 B/op 15 allocs/op
BenchmarkJsonCompressPack/decode#1024-20 187113 71612 ns/op 47640 B/op 23 allocs/op
BenchmarkJsonCompressPack/decode#16384-20 35790 346580 ns/op 173352 B/op 30 allocs/op

自定义二进制协议

对于序列化和反序列化在复用内存后,速度的提升非常明显,在同步的操作下,能做到 0 字节分配。异步场景下,使用 sync.Pool 内存固定字节分配(两个返回值在堆上分配)

BenchmarkBinaryPack/encode#72-20                  72744334             187.1 ns/op             144 B/op          2 allocs/op
BenchmarkBinaryPack/encode#1024-20 17048832 660.6 ns/op 1200 B/op 2 allocs/op
BenchmarkBinaryPack/encode#16384-20 2085050 6280 ns/op 18495 B/op 2 allocs/op
BenchmarkBinaryPack/encode_with_pool#72-20 34700313 109.2 ns/op 64 B/op 2 allocs/op
BenchmarkBinaryPack/encode_with_pool#1024-20 39370662 101.1 ns/op 64 B/op 2 allocs/op
BenchmarkBinaryPack/encode_with_pool#16384-20 18445262 177.2 ns/op 64 B/op 2 allocs/op
BenchmarkBinaryPack/encode_to#72-20 705428736 16.96 ns/op 0 B/op 0 allocs/op
BenchmarkBinaryPack/encode_to#1024-20 575312358 20.78 ns/op 0 B/op 0 allocs/op
BenchmarkBinaryPack/encode_to#16384-20 100000000 113.4 ns/op 0 B/op 0 allocs/op
BenchmarkBinaryPack/decode_meta#72-20 1000000000 2.890 ns/op 0 B/op 0 allocs/op
BenchmarkBinaryPack/decode_meta#1024-20 1000000000 2.886 ns/op 0 B/op 0 allocs/op
BenchmarkBinaryPack/decode_meta#16384-20 1000000000 2.878 ns/op 0 B/op 0 allocs/op
BenchmarkBinaryPack/decode_with_pool#72-20 106808395 31.51 ns/op 16 B/op 1 allocs/op
BenchmarkBinaryPack/decode_with_pool#1024-20 100319094 35.94 ns/op 16 B/op 1 allocs/op
BenchmarkBinaryPack/decode_with_pool#16384-20 26447718 138.6 ns/op 16 B/op 1 allocs/op

总结一下

通义千问的

Binary Pack:

- encode_to:性能最优,几乎没有内存分配,适用于高性能要求的场景。

- encode_with_pool:使用内存池优化,显著减少了时间和内存开销,适用于大多数场景。

- encode:标准方法,时间和内存开销较高。

MsgPack:

- encode_with_buf:使用预分配的缓冲区,显著减少了时间和内存开销,适用于大多数场景。

- encode:标准方法,时间和内存开销较高。

- decode:解码性能一般,内存开销较高。

Json Pack:

- encode_with_buf:使用预分配的缓冲区,显著减少了时间和内存开销,适用于大多数场景。

- encode:标准方法,时间和内存开销较高。

- decode:解码性能较差,内存开销较高。

Json Compress Pack:

- encode:标准方法,时间和内存开销非常高,不推荐用于高性能要求的场景。

- decode:解码性能较差,内存开销较高。

我总结的

在内网的环境进行传输,一般网络带宽不会成为瓶颈,所以可以不用考虑数据压缩,上面结果也看到压缩非常占用资源;

如果对数据内容不关心且数据量非常多的情况下(比如传输 pcap 包),那么使用自定义协议可能更合适一些,固定长度的元数据解析起来优化空间巨大,二进制解析比 json/msgpack 快内存分配也非常少。

引用

优化 Go 语言数据打包:性能基准测试与分析的更多相关文章

  1. KubeCon 2021|使用 eBPF 代替 iptables 优化服务网格数据面性能

    作者 刘旭,腾讯云高级工程师,专注容器云原生领域,有多年大规模 Kubernetes 集群管理及微服务治理经验,现负责腾讯云服务网格 TCM 数据面产品架构设计和研发工作. 引言 目前以 Istio[ ...

  2. DedeCMS数据负载性能优化方案简单几招让你提速N倍

    前文介绍了DedeCMS栏目列表页实现完美分页的方法,避免了大部分重复栏目标题对搜索引擎的影响,对SEO更有利.今天,分享一下DedeCMS数据负载性能优化的方法. 接触织梦也有三年多时间了,对它可谓 ...

  3. 大数据应用之HBase数据插入性能优化实测教程

    引言: 大家在使用HBase的过程中,总是面临性能优化的问题,本文从HBase客户端参数设置的角度,研究HBase客户端数据批量插入性能优化的问题.事实胜于雄辩,数据比理论更有说服力,基于此,作者设计 ...

  4. mongodb可以通过profile来监控数据 (mongodb性能优化)

    mongodb可以通过profile来监控数据 (mongodb性能优化)   开启 Profiling  功能 ,对慢查询进行优化: mongodb可以通过profile来监控数据,进行优化. 查看 ...

  5. 【转载】使用Exp和Expdp导出数据的性能对比与优化

    转自:http://blog.itpub.net/117319/viewspace-1410931/ 序:这方面的文章虽然很多人写过,但是结合实际进行详细的对比分析的不多,这里,结合所在公司的行业,进 ...

  6. oracle数据迁移之Exp和Expdp导出数据的性能对比与优化

    https://wangbinbin0326.github.io/2017/03/31/oracle%E6%95%B0%E6%8D%AE%E8%BF%81%E7%A7%BB%E4%B9%8BExp%E ...

  7. 【SQL server初级】数据库性能优化一:数据库自身优化(大数据量)

    数据库优化包含以下三部分,数据库自身的优化,数据库表优化,程序操作优化.此文为第一部分 数据库性能优化一:数据库自身优化 优化①:增加次数据文件,设置文件自动增长(粗略数据分区) 1.1:增加次数据文 ...

  8. 最全 webpak4.0 打包性能优化清单

    最全 webpak4.0 打包性能优化清单 webpack4.0如何进行打包优化? 无非是从两个角度进行优化,其一:优化打包速度,其二:优化打包体积,送你一份打包性能优化清单 1.使用loader的时 ...

  9. webpack 打包性能优化

    webpack 打包性能优化 开启多线程打包 thread-loader https://www.npmjs.com/package/thread-loader https://github.com/ ...

  10. SQL Server 性能优化之——系统化方法提高性能

    SQL Server 性能优化之——系统化方法提高性能 阅读导航 1. 概述 2. 规范逻辑数据库设计 3. 使用高效索引设计 4. 使用高效的查询设计 5. 使用技术分析低性能 6. 总结 1. 概 ...

随机推荐

  1. 顺序表_C

    // Code file created by C Code Develop #include "ccd.h" #include "stdio.h" #incl ...

  2. Win10下安装LabelImg以及使用--LabelImg

    labelImg是图片标注软件,用于数据集的制作.标注等等.下面介绍labelImg的安装过程. 我用的是anaconda,所以以anaconda prompt作为终端: 在Anaconda Prom ...

  3. 项目中的坑记录~v-if和v-show的坑

    有个功能是这样的,点击获取验证码,获取验证码之后将输入框禁用,进行倒计时11秒. 问题:第一次的倒计时是从6开始的, 之后的倒计时都是从9开始倒计,没有从11开始 解决:主要是用了v-show.倒计时 ...

  4. SpringBoot+ Sharding Sphere 轻松实现数据库字段加解密

    一.介绍 在实际的软件系统开发过程中,由于业务的需求,在代码层面实现数据的脱敏还是远远不够的,往往还需要在数据库层面针对某些关键性的敏感信息,例如:身份证号.银行卡号.手机号.工资等信息进行加密存储, ...

  5. Nacos 高级详解:提升你的开发和部署效率

    Nacos 高级 一 .服务集群 需求 服务提供者搭建集群 服务调用者,依次显示集群中各服务的信息 搭建 修改服务提供方的controller,打印服务端端口号 package com.czxy.co ...

  6. Umov移动方块-scratch编程作品

    程序说明: <Umov移动方块>是一款基于Scratch平台制作的小游戏.在这个游戏中,玩家将面对一个3×3的圆圈棋盘,并通过鼠标控制蓝色方块在这些圆圈中灵活移动.游戏的挑战在于,舞台的四 ...

  7. 从.net开发做到云原生运维(一)——从.net framework过渡到.net core

    1. 前言 序篇讲了自己的一些感悟和经历,从这章开始就开始讲一些.net技术栈的东西了. 2. .net framework和.net core对比 .NET Framework 概述 .NET Fr ...

  8. 英语表达中address和solve的区别

    "Address" 和 "solve" 都表示处理问题,但在具体用法和含义上有所不同: Address: 含义: 处理.应对.讨论或提及问题. 强调: 关注并开 ...

  9. 在哲学/自然科学范畴下“推理”(reason about)的类别及解释

    注意,本文的解释采用Google大模型(Gemini)的答案. 翻译: 推理是运用逻辑和证据得出结论的过程.它包含批判性地思考一个主题,考虑不同的观点,以及识别事物之间的关系.以下是推理的一些方式: ...

  10. 性能测试面试题大曝光,让你如何迅速拿下 offer!

    性能测试面试题精选 1. 以前做过性能测试么?请结合例子具体说明性能测试的流程 面试考察点:性能测试的流程 首选做性能测试的需求分析,明确性能测试的目标.范围.场景和性能指标(如响应时间.吞吐量.并发 ...