go variant底层原理深入解析
作者:用户杨杰 发布时间:2024-05-22 17:45:08
varint
今天本来在研究 OpenTelemetry 的基准性能测试 github.com/zdyj3170101…,测试不同网络协议:grpc, grpc-stream, http, websocket 在发送不同大小数据包时消耗 cpu,吞吐 的区别,由 tigrannajaryan 这位大神所写。
好奇翻了翻该大神的 github 仓库,发现了一个同样神奇的库。
这个库也是基准的性能测试,用来测试 go 中不同方法实现的 多类型变量 在消耗 cpu 以及 内存上的区别。
旨在实现一个能存储多类型变量并且具有最小 cpu 消耗以及 内存消耗的数据结构。
github.com/tigrannajar…
benchmarks
Interface: 接口
struct:struct,多个 field 存放不同类型的结构体
variant:该库的时间
struct
struct 是一个结构体,typ 表示当前结构体的类型,不同的 field 分别存储不同的类型。
type Variant struct {
typ variant.Type
bytes []byte
str string
i int
f float64
}
struct 还有两种不同的类型。
根据是否返回指针分别为 plainstruct 和 ptrstruct。
而 ptrstruct 相比 plainstruct 就多出一次内存分配,以及增加 cpu 耗时(栈上内存分配几个移位指令就能完成)。
func StringVariant(v string) Variant {
return Variant{typ: variant.TypeString, str: v}
}
func StringVariant(v string) *Variant {
return &Variant{typ: variant.TypeString, str: v}
}
进行 benchmark 后发现 plainstruct 已经 0 byte 分配了,我也想不出还有其他的优化思路。
yangjie05-mac:plainstruct jie.yang05$ go test -bench=. -benchmem plainstruct_test.go plainstruct.go
Variant size=64 bytes
goos: darwin
goarch: amd64
cpu: VirtualApple @ 2.50GHz
BenchmarkVariantIntGet-10 1000000000 0.3111 ns/op 0 B/op 0 allocs/op
BenchmarkVariantFloat64Get-10 1000000000 0.3117 ns/op 0 B/op 0 allocs/op
BenchmarkVariantIntTypeAndGet-10 1000000000 0.3189 ns/op 0 B/op 0 allocs/op
BenchmarkVariantStringTypeAndGet-10 141588165 8.435 ns/op 0 B/op 0 allocs/op
BenchmarkVariantBytesTypeAndGet-10 140932470 8.465 ns/op 0 B/op 0 allocs/op
BenchmarkVariantIntSliceGetAll-10 7293846 165.7 ns/op 640 B/op 1 allocs/op
BenchmarkVariantIntSliceTypeAndGetAll-10 7491408 170.6 ns/op 640 B/op 1 allocs/op
BenchmarkVariantStringSliceTypeAndGetAll-10 7061575 170.1 ns/op 640 B/op 1 allocs/op
variant
一个 variant 由指向真实数据的指针 ptr,一个紧凑的 lenandtype 同时表示长度和类型,这个数据结构还根据不同位的系统做了优化,以及 capOrVal(在slice类型数据时,就是 cap,非slice类型数据时就是val )。
32位系统下,type 占3位,len 用29位表示
64 位系统下,type占3位,len用63位表示。
Variant 设计主要是为了同时满足存储 float64 和 string 的需求。 因为 float64 的存在,必须要有一个 int64 类型的字段存储 float64 的值。 而 string 的 len 是int类型的字段,就不需要用int64。
type Variant struct {
// Pointer to the slice start for slice-based types.
ptr unsafe.Pointer
// Len and Type fields.
// Type uses `typeFieldBitCount` least significant bits, Len uses the rest.
// Len is used only for the slice-based types.
lenAndType int
// Capacity for slice-based types, or the value for other types. For Float64Val type
// contains the 64 bits of the floating point value.
capOrVal int64
}
比如创建一个string的时候,ptr 中存放指向数据的指针,而lenAndType 中存储slice的长度以及 type。 ``
// NewString creates a Variant of TypeString type.
func NewString(v string) Variant {
hdr := (*reflect.StringHeader)(unsafe.Pointer(&v))
if hdr.Len > maxSliceLen {
panic("maximum len exceeded")
}
return Variant{
ptr: unsafe.Pointer(hdr.Data),
lenAndType: (hdr.Len << typeFieldBitCount) | int(TypeString),
}
}
为什么 variant 要比 plainstruct 快
分别测试 variant 和 plainstruct 创建 string 的性能:
func createVariantString() Variant { // 防止编译优化掉?
for i := 0; i < 1; i++ {
return StringVariant(testutil.StrMagicVal)
}
return StringVariant("def")
}
func BenchmarkVariantStringTypeAndGet(b *testing.B) {
for i := 0; i < b.N; i++ {
v := createVariantString()
if v.Type() == variant.TypeString {
if v.String() == "" {
panic("empty string")
}
} else {
panic("invalid type")
}
}
}
使用 go tool 做性能测试,并查看plainstruct的profile文件:
go test -o=bin -bench=. -v -test.cpuprofile=cpuprofile plainstruct_test.go plainstruct.go
go tool pprof -http=: bin cpuprofile
同理 variant:
go test -o=bin -bench=. -v -test.cpuprofile=cpuprofile variant_test.go variant.go variant_64.go
go tool pprof -http=: bin cpuprofile
variant 的汇编:
plainstruct的汇编:
主要区别还是plainstrutc的指令数太多,因为struct的字段更多。
variant 可能的优化?
variant 其实这里还有一个优化的方向,就是在 32 位机器存储 float64 的时候。 将 float64 拆成两个 int32,分别用 ptr 和 capOrVal 来存储。 这样在 32位系统下,capOrVal 可以由 int64 变成 int,节省了 4 个字节。
type Variant struct {
// Pointer to the slice start for slice-based types.
ptr unsafe.Pointer
// Len and Type fields.
// Type uses `typeFieldBitCount` least significant bits, Len uses the rest.
// Len is used only for the slice-based types.
lenAndType int
capOrVal int
}
来源:https://juejin.cn/post/7101277566031527943


猜你喜欢
- 一、什么是主键、外键: 关系型数据库中的一条记录中有若干个属性,若其中某一个属性组(注意是组)能唯一标识一条记录,该属性组就可以成为一个主键
- 前言设置mysql最大连接数的方法:首先打开mysql的控制台;然后输入语句【set GLOBAL max_connections=1000
- 目录一、线程基础以及守护进程二、线程锁(互斥锁)三、线程锁(递归锁)四、死锁五、队列六、相关面试题七、判断数据是否安全八、进程池 &
- 前言:线性回归模型属于经典的统计学模型,该模型的应用场景是根据已知的变量(即自变量)来预测某个连续的数值变量(即因变量)。例如餐厅根据媒体的
- 用vue2.0开发做兼容时,你可能会发现vue项目在IE11版本浏览器中是空白的。。。看到空白的页面你可能会懵几秒钟,没事,下面我们就来解决
- 一、什么是Django ContentTypes?Django ContentTypes是由Django框架提供的一个核心功能,它对当前项目
- 1 介绍在设计到数据库的开发中,难免要将图片或音频文件插入到数据库中的情况。一般来说,我们可以同过插入图片文件相应的存储位置,而不是文件本身
- 一,fso.GetFile提取文件相应的 File 对象1,getfile.asp<%whichfile=Serv
- 一、Python的矩阵传播机制(Broadcasting)我们知道在深度学习中经常要操作各种矩阵(matrix) 。回想一下,我们
- Python 对代码的缩进要求非常严格,同一个级别代码块的缩进量必须一样,否则解释器会报 SyntaxError 异常错误。在 Python
- Date 日期和时间对象1. 介绍Date对象,是操作日期和时间的对象。Date对象对日期和时间的操作只能通过方法。2. 构造函数2.1 n
- 在urls.py文件中按照如下步骤写,即可正确使用DRF的内置路由.from .views import BookModel # 1. 导入
- ASP 能快速执行你的 * 页,但你还可以通过紧缩代码和数据库连接以使它们执行更快。这是一篇关于怎样精简代码和Asp 特征以获得最快执行速度
- 前言在日常开发中,客户端上传文件的一般流程是:客户端向服务端发送文件,再由服务端将文件转储到专门的存储服务器或云计算厂商的储存服务(例如阿里
- 前提搭建钉钉应答机器人,需要先准备或拥有以下权限:钉钉企业的管理员或子管理员(如果不是企业管理员,可以自己创建一个企业,很方便的)有公网通信
- 环境准备python3.6PyCharm 2017.1.3Windows环境框架搭建selenium3.6安装方法:pip install
- 在安装了IIS以后,缺省的服务器端脚本语言被设置成VBScript。许多Web 开发团队在他们的开发环境中保持了这些缺省设置,这是不幸的,因
- 前言最近有人对自动上传与发布很感兴趣,都私下找我说了好几次了。今天,必须把他安排,必须实力宠粉。“本篇依次介绍目前主流的
- TensorBoard是用于可视化图形和其他工具以理解、调试和优化模型的界面。它是一种为机器学习工作流提供测量和可视化的工具。它有助于跟踪损
- 一. meta方法打包好的入口index.html头部加入<META HTTP-EQUIV="pragma" CO