go variant底层原理深入解析

 更新时间:2022年11月23日 11:10:11   作者:用户杨杰  
这篇文章主要为大家介绍了go variant底层原理深入解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

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
}

以上就是go variant原理深入解析的详细内容,更多关于go variant的资料请关注脚本之家其它相关文章!

相关文章

  • GO语言类型转换和类型断言实例分析

    GO语言类型转换和类型断言实例分析

    这篇文章主要介绍了GO语言类型转换和类型断言,以实例形式详细分析了类型转换和类型断言的概念与使用技巧,需要的朋友可以参考下
    2015-01-01
  • Golang map实践及实现原理解析

    Golang map实践及实现原理解析

    这篇文章主要介绍了Golang map实践以及实现原理,Go 语言中,通过哈希查找表实现 map,用链表法解决哈希冲突,本文结合实例代码给大家介绍的非常详细,需要的朋友参考下吧
    2022-06-06
  • Golang使用Consul详解

    Golang使用Consul详解

    Consul是一个服务发现软件, 提供了服务发现\键值存储\健康检查等功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • go编程中go-sql-driver的离奇bug解决记录分析

    go编程中go-sql-driver的离奇bug解决记录分析

    这篇文章主要为大家介绍了go编程中go-sql-driver的离奇bug解决记录分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • 在Go语言中实现DDD领域驱动设计实例探究

    在Go语言中实现DDD领域驱动设计实例探究

    本文将详细探讨在Go项目中实现DDD的核心概念、实践方法和实例代码,包括定义领域模型、创建仓库、实现服务层和应用层,旨在提供一份全面的Go DDD实施指南
    2024-01-01
  • 深入理解Golang中WebSocket和WSS的支持

    深入理解Golang中WebSocket和WSS的支持

    本文主要介绍了深入理解Golang中WebSocket和WSS的支持,实现了Golang构建WebSocket服务器和客户端,并使用自签名证书实现WSS的功能,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • Golang库插件注册加载机制的问题

    Golang库插件注册加载机制的问题

    这篇文章主要介绍了Golang库插件注册加载机制,这里说的插件并不是指的golang原生的可以在buildmode中加载指定so文件的那种加载机制,需要的朋友可以参考下
    2022-03-03
  • Go 的入口函数和包初始化的使用

    Go 的入口函数和包初始化的使用

    本文主要介绍了Go 的入口函数和包初始化的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • golang实现sql结果集以json格式输出的方法

    golang实现sql结果集以json格式输出的方法

    这篇文章主要介绍了golang实现sql结果集以json格式输出的方法,涉及Go语言针对sql结果集的遍历、转换及json格式相关操作技巧,需要的朋友可以参考下
    2017-03-03
  • 一篇文章说清楚 go get 使用私有库的方法

    一篇文章说清楚 go get 使用私有库的方法

    这篇文章主要介绍了go get 如何使用私有库,本文会明确指出Git 、golang的配置项,附送TortoiseGit + Git混合配置,需要的朋友可以参考下
    2022-09-09

最新评论