优化 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"`// InterfaceIndexInterfaceIndex int `json:"iface_idx" msgpack:"iface_idx"`
}type CapturePacket struct {CaptureInfoId uint32 `json:"id" msgpack:"id"`Data []byte `json:"data" msgpack:"data"`
}
有一个细节待敲定,抓到的包使用什么结构发送至服务端?json/msgpack/自定义格式?
json/msgpack 都有对应的规范,通用性强,不容易出 BUG,性能会差一点。自定义格式相比 json/msgpack 而言,可以去掉不必要的字段,连 key 都可以不用在序列化中出现,并且可以通过一些优化减少内存的分配,缓解gc压力。
自定义二进制协议优化思路如下
-
CaptureInfo/Id 字段直接固定N个字节表示,对于 CaptureLength/Length 可以直接使用 2 个字节来表达,Id 如果数量很少使用 1 个字节来表达都可以
-
内存复用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 []byteputfn 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快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构
相关文章:
优化 Go 语言数据打包:性能基准测试与分析
场景:在局域网内,需要将多个机器网卡上抓到的数据包同步到一个机器上。 原有方案:tcpdump -w 写入文件,然后定时调用 rsync 进行同步。 改造方案:使用 Go 重写这个抓包逻辑及同步逻辑,直接将抓到的包通过网…...
【SQL】未订购的客户
目录 语法 需求 示例 分析 代码 语法 SELECT columns FROM table1 LEFT JOIN table2 ON table1.common_field table2.common_field; LEFT JOIN(或称为左外连接)是SQL中的一种连接类型,它用于从两个或多个表中基于连接条件返回左表…...
Qt(9.28)
widget.cpp #include "widget.h"Widget::Widget(QWidget *parent): QWidget(parent) {QPushButton *btn1 new QPushButton("登录",this);this->setFixedSize(640,480);btn1->resize(80,40);btn1->move(200,300);btn1->setIcon(QIcon("C:…...
javascript-冒泡排序
前言:好久没学习算法了,今天看了一个视频课,之前掌握很好的冒泡排序居然没写出来? <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport"…...
第九届蓝桥杯嵌入式省赛程序设计题解析(基于HAL库)
一.题目分析 (1).题目 (2).题目分析 按键功能分析----存储位置的切换键 a. B1按下切换存储位置,切换后定时时间设定为当前位置存储的时间 b. B2短按切换时分秒高亮,设置完成后,长按把设置的时…...
MATLAB云计算集成:在云端扩展计算能力
摘要 MATLAB云计算集成是指将MATLAB的计算能力与云平台的弹性资源相结合,以实现高性能计算、数据处理和算法开发。本文详细介绍了MATLAB云计算的基本概念、优势、配置要点以及编程实践。 1. 云计算概述 云计算是一种通过互联网提供计算资源(如服务器、…...
基于BeagleBone Black的网页LED控制功能(flask+gpiod)
目录 项目介绍硬件介绍项目设计开发环境功能实现控制LED外设构建Webserver 功能展示项目总结 👉 【Funpack3-5】基于BeagleBone Black的网页LED控制功能 👉 Github: EmbeddedCamerata/BBB_led_flask_web_control 项目介绍 基于 BeagleBoard Black 开发板…...
【C语言】单片机map表详细解析
1、RO Size、RW Size、ROM Size分别是什么 首先将map文件翻到最下面,可以看到 1.1 RO Size:只读段 Code:程序的代码部分(也就是 .text 段),它存放了程序的指令和可执行代码。 RO Data:只读…...
Java中的继承和实现
Java中的继承和实现在面向对象编程中扮演着不同的角色,它们之间的主要区别可以从以下几个方面进行阐述: 1. 定义和用途 继承(Inheritance):继承是面向对象编程中的一个基本概念,它允许我们定义一个类&…...
uniapp云打包
ios打包 没有mac电脑,使用香蕉云编 先登录香蕉云编这个工具,新建csr文件——把csr文件下载到你电脑本地: 然后,登录苹果开发者中心 生成p12证书 1、点击+号创建证书 创建证书的时候一定要选择ios distribution app store and ad hoc类型的证书 2、上传刚才从本站生成的…...
端口安全技术原理与应用
目录 概述 端口安全原理 端口安全术语 二层安全地址配置 端口模式下配置 全局模式下配置 动态学习 二层数据包处理流程 三层安全地址配置 三层数据包处理流程 端口安全违例动作和安全地址老化时间 查看命令 端口安全的注意事项 小结 概述 园区网的接入安全关系着…...
数据集-目标检测系列-鲨鱼检测数据集 shark >> DataBall
数据集-目标检测系列-鲨鱼检测数据集 shark >> DataBall 数据集-目标检测系列-鲨鱼检测数据集 shark 数据量:6k 数据样例项目地址: gitcode: https://gitcode.com/DataBall/DataBall-detections-100s/overview github: https://github.com/Te…...
数字乡村解决方案-3
1. 国家大数据战略与数字乡村 中国第十三个五年规划纲要强调实施国家大数据战略,加快建设数字中国,推进数据资源整合和开放共享,保障数据安全,以大数据助力产业转型升级和提高社会治理的精准性与有效性。 2. 大数据与数字经济 …...
WPF文本框无法输入小数点
问题描述 在WPF项目中,文本框BInding双向绑定了数据Text“{UpdateSourceTriggerPropertyChanged}”,但手套数据是double类型,手动输入数据时,小数点输入不进去 解决办法: 在App.xaml.cs文件中添加语句: …...
R开头的后缀:RE
RE表示方位上的向后,一种时空上的折返,和表示否定意味的不。 68.re- 空间顺序 ①表示"向后,相反,不" RE表示正向抵抗的力的词语,和情绪的词语,用来表示一种极力的反抗和拒绝,包括…...
Vue2配置环境变量的注意事项
在实际开发中时常会遇到需要开发环境与生产环境中一些参数的替换,为了方便线上线下环境变量切换可以利用node中的process进行环境变量管理 实现步骤如下: 1.在 根目录 新增环境文件 .env.development 和 .env.production 注意文件名称保持一致( 需要强调的是文件中的变量名切…...
机器学习:探索未知边界,解锁智能潜力
欢迎来到 破晓的历程的 博客 ⛺️不负时光,不负己✈️ 在这个日新月异的科技时代,机器学习作为人工智能领域的核心驱动力,正以前所未有的速度改变着我们的世界。从智能家居的个性化推荐到自动驾驶汽车的精准导航,从医疗诊断的辅助…...
万户OA-ezOFFICE fileUpload.controller 任意文件上传漏洞复现
0x01 产品描述: 万户OA(Office Automation)是一款企业级协同办公管理软件,旨在为企业提供全面的办公自动化解决方案。万户ezOFFICE存在任意文件上传漏洞。攻击者可以通过该远程下载任意文件到目标服务器,导致攻击者可获…...
Time-MoE : 时间序列领域的亿级规模混合专家基础模型
Time-MoE : 时间序列领域的亿级规模混合专家基础模型 时间序列预测一直是量化研究和工业应用中的重要课题。随着深度学习技术的发展,大规模预训练模型在自然语言处理和计算机视觉领域取得了显著进展,但在时间序列预测领域,这些模型的规模和运…...
Spring Boot入门指南
前言 Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。本文将详细介绍 Spring Boot 的基本概念、环境搭建、第一…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...
从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
