go variant底层原理深入解析

目录
  • varint
  • benchmarks
  • struct
  • variant
    • 为什么 variant 要比 plainstruct 快
  • 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的资料请关注我们其它相关文章!

(0)

相关推荐

  • Golang Copier入门到入坑探究

    目录 正文 安装 快速入门 入坑 再探坑出坑 再盘一盘坑 结语 正文 github: https://github.com/jinzhu/copier 由于 golang 没有对复杂结构体的 clone 方法,所以,就需要有 copier 这样的工具库. 它看起来很简单,但实际使用中,有些“坑”还是要注意! 本文: 入门为辅,探“坑”为主, 看完再划走,CS我没有. 安装 go get github.com/jinzhu/copier 快速入门 好的,来一段代码快速了解 copier packa

  • Android Studio gradle配置packagingOptions打包so库重复

    目录 正文 pickFirst 匹配 doNotStrip 设置 merge 将匹配的文件都添加到APK中 exclude 过滤 正文 在安卓开发中,通常会使用到gradle来编译,在安卓项目的app目录下的build.gradle中是用来对编译进行配置的,packagingOptions 是其中的一个打包配置,常见的设置项有exclude.pickFirst.doNotStrip.merge. 在日常代码开发中,我们需要知其然,而知其所以然,本文章知识也是Android日常瘦身的的必备知识.

  • Go语言编程通过dwarf获取内联函数

    目录 dwarf组成 如何将 addr 转换为行号 内联函数 如何展开内联函数 使用 parca 展开内联函数 parca 输出有以下问题 dwarf组成 dwarf 由 The Debugging Information Entry . type Entry struct { Offset Offset Tag Tag // 描述其类型 Children bool Field []Field // 包含的字段 } 不同的 entry 有不同的类型: tag compile unit, 在 go

  • Go语言defer的一些神奇规则示例详解

    目录 测试题 分析 规则一当defer被声明时,其参数就会被实时解析 规则二 defer可能操作主函数的具名返回值 规则三 延迟函数执行按后进先出顺序执行 坑实例 测试题 defer有一些规则,如果不了解,代码实现的最终结果会与预期不一致.对于这些规则,你了解吗? 这是关于defer使用的代码,可以先考虑一下返回值. package main import ( "fmt" ) /** * @Author: Jason Pang * @Description: 快照 */ func de

  • Dragonfly P2P 传输协议优化代码解析

    目录 优化背景 相关代码分析 优化方案 优化实现 优化结果 优化背景 此前 Dragonfly 的 P2P 下载采用静态限流策略,相关配置项在 dfget.yaml 配置文件中: # 下载服务选项. download: # 总下载限速. totalRateLimit: 1024Mi # 单个任务下载限速. perPeerRateLimit: 512Mi 其中 perPeerRateLimit 为单个任务设置流量上限, totalRateLimit 为单个节点的所有任务设置流量上限. 静态限流策略

  • go variant底层原理深入解析

    目录 varint benchmarks struct variant 为什么 variant 要比 plainstruct 快 variant 可能的优化? varint 今天本来在研究 OpenTelemetry 的基准性能测试 github.com/zdyj3170101…,测试不同网络协议:grpc, grpc-stream, http, websocket 在发送不同大小数据包时消耗 cpu,吞吐 的区别,由 tigrannajaryan 这位大神所写. 好奇翻了翻该大神的 githu

  • Spring Boot 底层原理基础深度解析

    目录 1. 底层注解@Configuration 2. 底层注解@Import 3. 底层注解@Conditional 1. 底层注解@Configuration @Configuration 注解主要用于给容器添加组件(Bean),下面实践其用法: 项目基本结构: 两个Bean组件: User.java package com.menergy.boot.bean; /** * 用户 */ public class User { private String name; private Inte

  • 通过实例解析JMM和Volatile底层原理

    这篇文章主要介绍了通过实例解析JMM和Volatile底层原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 JMM和volatile分析 1.JMM:Java Memory Model,java线程内存模型 JMM:它是一个抽象的概念,描述的是线程和内存间的通信,java线程内存模型和CPU缓存模型类似,它是标准化的,用于屏蔽硬件和操作系统对内存访问的差异性. 2.JMM和8大原子操作结合 3.volatile的应用及底层原理探究 volat

  • Activiti工作流学习笔记之自动生成28张数据库表的底层原理解析

    网上关于工作流引擎Activiti生成表的机制大多仅限于四种策略模式,但其底层是如何实现的,相关文章还是比较少,因此,觉得撸一撸其生成表机制的底层原理. 我接触工作流引擎Activiti已有两年之久,但一直都只限于熟悉其各类API的使用,对底层的实现,则存在较大的盲区. Activiti这个开源框架在设计上,其实存在不少值得学习和思考的地方,例如,框架用到以命令模式.责任链模式.模板模式等优秀的设计模式来进行框架的设计. 故而,是值得好好研究下Activiti这个框架的底层实现. 我在工作当中现

  • Python matplotlib底层原理解析

    目录 1. matplotlib 框架组成 2. 脚本层(scripting) 3. 美工层(artist) 4. 后端层(backend) 复习回顾: 前期,我们已经学习了matplotlib模块相关的基础知识,对 matplotlib 模块折线图.饼图.柱状图进行操作. 我们都知道matplotlib 是偏向底层用于可视化数据处理的库,我们在绘制图表的时候主要步骤主要有四大步骤: 导入 matplotlib.pplot库 使用pandas/numpy模块对数据进行整分析理 调用pyplot中

  • Go反射底层原理及数据结构解析

    目录 1. 反射的引入与介绍 2. 反射的数据结构 3. 如何通过反射对象来修改原数据对象的值? 1. 反射的引入与介绍 在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问.检测和修改它本身状态或行为的一种能力.用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为. 需要反射的 2 个常见场景: 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好:也可能是传入的类型很多,这些类型并不能统一表示.这时反射就会用的上了. 有时候需要根据某些条

  • Golang底层原理解析String使用实例

    目录 引言 String底层 stringStruct结构 引言 本人因为种种原因(说来听听),放弃大学学的java,走上了golang这条路,本着干一行爱一行的情怀,做开发嘛,不能只会使用这门语言,所以打算开一个底层原理系列,深挖一下,狠狠的掌握一下这门语言 废话不多说,上货 String底层 既然研究底层,那就得全方面覆盖,必须先搞一下基础的东西,那必须直接基本数据类型走起啊, 字符串String的底层我看就很基础 string大家应该都不陌生,go中的string是所有8位字节字符串的集合

  • SpringBoot整合log4j日志与HashMap的底层原理解析

    一,SpringBoot与日志 1.springboot整合log4j日志记录 1.在resources目录下面创建日志文件,并引入: 代码如下(示例): #log4j.rootLogger=CONSOLE,info,error,DEBUG log4j.rootLogger=info,error,CONSOLE,DEBUG log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=o

  • Docker基本概念和底层原理解析

    目录 1.Docker的底层原理 2.Docker中常用的基本概念 3.run命令的运行流程 4.为什么Docker比VM快 Docker架构图: 我们依照Docker架构图进行Docker基础概念的说明. 1.Docker的底层原理 Docker是一个Client-Server结构的系统,Docker守护进程运行在主机上,然后通过Socket连接从客户端访问,守护进程从客户端接受命令并管理运行在主机上的容器.容器是一个运行时环境,就好比是我们前面说到的集装箱. 例如架构图中的客户端(Clien

  • hashset去除重复值原理实例解析

    Java中的set是一个不包含重复元素的集合,确切地说,是不包含e1.equals(e2)的元素对.Set中允许添加null.Set不能保证集合里元素的顺序. 在往set中添加元素时,如果指定元素不存在,则添加成功.也就是说,如果set中不存在(e==null?e1==null:e.queals(e1))的元素e1,则e1能添加到set中. 下面以set的一个实现类HashSet为例,简单介绍一下set不重复实现的原理: package com.darren.test.overide; publ

随机推荐