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:该库的时间

go 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 的汇编:

go variant底层原理深入解析

plainstruct的汇编:

go variant底层原理深入解析

主要区别还是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

标签:go,variant,底层原理
0
投稿

猜你喜欢

  • js利用递归与promise 按顺序请求数据的方法

    2024-06-05 09:34:58
  • 关于tensorflow softmax函数用法解析

    2022-10-29 07:42:09
  • Python爬虫工具requests-html使用解析

    2021-10-23 02:20:27
  • 基于Python实现开心消消乐小游戏的示例代码

    2022-03-20 13:19:10
  • pycharm查看变量值的4种方法汇总

    2022-04-17 04:37:37
  • Keras使用tensorboard显示训练过程的实例

    2023-07-15 18:57:35
  • 一篇文章带你了解清楚Mysql 锁

    2024-01-24 21:17:43
  • 浅谈Scrapy网络爬虫框架的工作原理和数据采集

    2023-08-24 22:17:21
  • 了解WEB页面工具语言XML(四)应用分类

    2008-09-05 17:18:00
  • 全面了解Python的getattr(),setattr(),delattr(),hasattr()

    2021-01-11 11:30:33
  • Python数据正态性检验实现过程

    2022-07-10 15:46:14
  • asp中提示至少一个参数没有被指定值 解决方法

    2013-06-01 20:14:00
  • python 画三维图像 曲面图和散点图的示例

    2022-04-13 01:28:16
  • django和flask哪个值得研究学习

    2021-10-02 16:52:46
  • js序列化和反序列化的使用讲解

    2023-08-05 08:13:24
  • MySQL修改默认字符集

    2010-11-02 12:11:00
  • ORACLE应用经验(2)

    2010-07-31 13:31:00
  • Server 对象 错误 ASP 0177 800401f3 的解决方案 Server 对象 错误 ASP 0177 800401f3

    2009-07-28 17:57:00
  • 教你轻松恢复/修复SQL Server的MDF文件

    2024-01-28 17:30:49
  • Pygame实战练习之保护单身狗游戏

    2022-07-04 03:30:28
  • asp之家 网络编程 m.aspxhome.com