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

news/2024/9/28 0:19:43 标签: golang, 开发语言, 后端

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

原有方案: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. 内存复用Encode 逻辑内部不分配内存,这样直接写入外部的 buffer,如果外部 buffer 是同步操作的话,整个逻辑 0 内存分配Decode 内部不分配内存,只解析元数据和复制 Data 切片,如果外部是同步操作,同样整个过程 0 内存分配如果是异步操作,那么在调用 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字节),编码后增加了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

json压缩

在内网的情况下,带宽不是问题,这个压测结果直接被 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.887 ns/op           0 B/op      0 allocs/op
BenchmarkBinaryPack/decode_meta#1024-20         1000000000        2.882 ns/op           0 B/op      0 allocs/op
BenchmarkBinaryPack/decode_meta#16384-20        1000000000        2.876 ns/op           0 B/op      0 allocs/op
BenchmarkBinaryPack/decode#72-20                 100000000        85.63 ns/op          80 B/op      1 allocs/op
BenchmarkBinaryPack/decode#1024-20                 7252350        445.4 ns/op        1024 B/op      1 allocs/op
BenchmarkBinaryPack/decode#16384-20                 554329         5499 ns/op       16384 B/op      1 allocs/op
BenchmarkBinaryPack/decode_with_pool#72-20       109352595        33.97 ns/op          16 B/op      1 allocs/op
BenchmarkBinaryPack/decode_with_pool#1024-20      85589674        36.27 ns/op          16 B/op      1 allocs/op
BenchmarkBinaryPack/decode_with_pool#16384-20     26163607        140.4 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 快内存分配也非常少。

文章转载自:文一路挖坑侠

原文链接:https://www.cnblogs.com/shuqin/p/18427020

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构


http://www.niftyadmin.cn/n/5679906.html

相关文章

通义千问:让我的编程工作效率翻倍的秘密武器

在日益繁忙的工作环境中&#xff0c;选择合适的编程工具已成为提升开发者工作效率的关键。不同的工具能够帮助我们简化代码编写、自动化任务、提升调试速度&#xff0c;甚至让团队协作更加顺畅。在这篇博客中&#xff0c;我将分享一个让我工作效率翻倍的编程工具——通义千问大…

C#和数据库高级:虚方法

文章目录 一、抽象方法和抽象类中的思考1.1、回顾抽象方法的特点1.2、针对抽象方法问题的引出 二、虚方法的使用步骤2.1、虚方法重写方法的调用2.2、系统自带的虚方法2.3、重写Equals方法2.4、虚方法和抽象方法的比较 三、虚方法和抽象方法的联系3.1、ToString()方法的应用 一、…

利用 Local Data 导入文件到 OceanBase 的方法

背景 在很多传统方法中&#xff0c;数据的传输常依赖于csv格式。为了提高传输效率&#xff0c;属于同一张表的多个csv文件往往会被打包成gz文件进行传输。 当gz文件从上游传递到下游后&#xff0c;为了将其中的csv数据导入数据库&#xff0c;一种直接的做法是&#xff1a; 1…

class 026 哈希表、有序表和比较器的用法

这篇文章是看了“左程云”老师在b站上的讲解之后写的, 自己感觉已经能理解了, 所以就将整个过程写下来了。 这个是“左程云”老师个人空间的b站的链接, 数据结构与算法讲的很好很好, 希望大家可以多多支持左程云老师, 真心推荐. https://space.bilibili.com/8888480?spm_id_f…

CPU中也应用到了缓存:CPU3层高速缓存,以及它的缓存一致性问题、MESI协议和Java的一些应用

为什么需要cpu高速缓存&#xff1f; 缓存&#xff0c;一般是为了用来协调两端的数据传输效率差&#xff08;也可以归纳为性能差&#xff09;&#xff0c;提升响应速度。那么CPU的高速缓存是用来协调什么之间的速度差呢&#xff1f; CPU在处理一条指令的时候&#xff0c;会读写…

webpack 4 的 30 个步骤构建 react 开发环境

将 react 和 webpack4 进行结合&#xff0c;集 webpack 的优势于一身&#xff0c;从 0 开始构建一个强大的 react 开发环境。 其实很多人都有 一看就会&#xff0c;一做就废 的特点(当然也包括我在内)&#xff0c;这个时候&#xff0c;你需要制定一个略微详细的计划&#xff0…

使用腾讯地图,在地图上圈选标记半径范围

1、引入地图&#xff1a; <!-- 引入腾讯地图 --> <script charset"utf-8" src"https://map.qq.com/api/gljs?v1.exp&keyYOUR_KEY&librariesvisualization,service"></script> 2、具体实现如下&#xff1a; export default {…

linux下的分布式Minio部署实践

Linux下的分布式Minio部署实践 分布式Minio部署可以将多块硬盘&#xff08;位于相同机器或者不同机器&#xff09;组成一个对象存储服务&#xff0c;避免单机环境下硬盘容量不足、单点故障等问题。 1. 简介 在当前的云计算和大数据时代&#xff0c;IT系统通常的设计理念都是…