go性能优化

2023-11-04

常规手段

1.sync.Pool

临时对象池应该是对可读性影响最小且优化效果显著的手段。最典型的就是fasthttp了,它几乎把所有的对象都用sync.Pool维护。
但这样的复用不一定全是合理的。比如在fasthttp中,传递上下文相关信息的RequestCtx就是用sync.Pool维护的,这就导致了你不能把它传递给其他的goroutine
如果要在fasthttp中实现类似接受请求->异步处理的逻辑,必须得拷贝一份RequestCtx再传递。这对不熟悉fasthttp原理的使用者来讲,很容易就踩坑了。

另外,在优化前要善用go逃逸检查分析对象是否逃逸到堆上,防止负优化。

2.string2bytes & bytes2string

这也是两个比较常规的优化手段,核心还是复用对象,减少内存分配。

在go标准库中也有类似的用法gostringnocopy

要注意string2bytes后,不能对其修改。

unsafe.Pointer经常出现在各种优化方案中,使用时要非常小心。这类操作引发的异常,通常是不能recover的。

3.协程池

绝大部分应用场景,go是不需要协程池的。当然,协程池还是有一些自己的优势:

  1. 可以限制goroutine数量,避免无限制的增长。
  2. 减少栈扩容的次数。
  3. 频繁创建goroutine的场景下,资源复用,节省内存。(需要一定规模。一般场景下,效果不太明显)

go对goroutine有一定的复用能力。所以要根据场景选择是否使用连接池,不恰当的场景不仅得不到收益,反而增加系统复杂性

4.反射

go里面的反射代码可读性本来就差,常见的优化手段进一步牺牲可读性。
而且后续马上就有范型的支持,所以若非必要,建议不要优化反射部分的代码

比较常见的优化手段有:

  1. 缓存反射结果,减少不必要的反射次数。例如json-iterator
  2. 直接使用unsafe.Pointer根据各个字段偏移赋值
  3. 消除一般的struct反射内存消耗go-reflect
  4. 避免一些类型转换,如interface->[]byte。可以参考zerolog

5.减小锁消耗

并发场景下,对临界区加锁比较常见。带来的性能隐患也必须重视。常见的优化手段有:

  1. 减小锁力度:
    go标准库当中,math.rand就有这么一处隐患。当我们直接使用rand库生成随机数时,实际上由全局的globalRand对象负责生成。globalRand加锁后生成随机数,会导致我们在高频使用随机数的场景下效率低下。
  2. atomic:
    适当场景下,用原子操作代替互斥锁也是一种经典的lock-free技巧。

    标准库中sync.map针对读操作的优化消除了rwlock,是一个标准的案例。对它的介绍文章也比较多,不在赘述。

    prometheus里的组件histograms直方图也是一个非常巧妙的设计。

    一般的开源库,比如go-metrics都是直接在这里使用了互斥锁。指标上报作为一个高频操作,在这里加锁,对系统性能影响可想而知。

    参考sync.map里冗余map的做法,prometheus把原来histograms的计数器也分为两个:coldhot,还有一个hotIdx用来表示哪个计数器是hot
    业务代码上报指标时,用atomic原子操作对hot计数器累加
    prometheus服务上报数据时,更改hotIdx,把原来的热数据变为冷数据,作为上报的数据。然后把现在冷数据里的值,累加到热数据里,完成一次冷热数据的更新替换。

    还有一些状态等待,结构体内存布局的介绍,不再赘述。具体可以参考Lock-free Observations for Prometheus Histograms

另类手段

golink在官方的文档里有介绍,使用格式:

//go:linkname FastRand runtime.fastrand
func FastRand() uint32

主要功能就是让编译器编译的时候,把当前符号指向到目标符号。上面的函数FastRand被指向到runtime.fastrand

runtime包生成的也是伪随机数,和math包不同的是,它的随机数生成使用的上下文是来自当前goroutine的,所以它不用加锁。正因如此,一些开源库选择直接使用runtime的随机数生成函数。性能对比如下:

Benchmark_MathRand-12       84419976            13.98 ns/op
Benchmark_Runtime-12        505765551           2.158 ns/op

还有很多这样的例子,比如我们要拿时间戳的话,可以标准库中的time.Now(),这个库在会有两次系统调用runtime.walltime1runtime.nanotime,分别获取时间戳和程序运行时间。大部分场景下,我们只需要时间戳,这时候就可以直接使用runtime.walltime1。性能对比如下:

Benchmark_Time-12       16323418            73.30 ns/op
Benchmark_Runtime-12    29912856            38.10 ns/op

同理,如果我们需要统计某个函数的耗时,也可以直接调用两次runtime.nanotime然后相减,不用再调用两次time.Now

//go:linkname nanotime1 runtime.nanotime1
func nanotime1() int64
func main() {
    defer func( begin int64) {
        cost := (nanotime1() - begin)/1000/1000
        fmt.Printf("cost = %dms \n" ,cost)
    }(nanotime1())
    
    time.Sleep(time.Second)
}

运行结果:cost = 1000ms 

2. log-函数名称行号的获取

虽然很多高性能的日志库,默认都不开启记录行号。但实际业务场景中,我们还是觉得能打印最好。

runtime中,函数行号和函数名称的获取分为两步:

  1. runtime回溯goroutine栈,获取上层调用方函数的的程序计数器(pc)。
  2. 根据pc,找到对应的funcInfo,然后返回行号名称

经过pprof分析。第二步性能占比最大,约60%。针对第一步,我们经过多次尝试,并没有找到有效的办法。但是第二步很明显,我们不需要每次都调用runtime函数去查找pc和函数信息的,我们可以把第一次的结果缓存起来,后面直接使用。这样。第二步约60%的消耗就可以去掉。

var(
    m sync.Map
)
func Caller(skip int)(pc uintptr, file string, line int, ok bool){
    rpc := [1]uintptr{}
    n := runtime.Callers(skip+1, rpc[:])
    if n < 1 {
        return
    }
    var (
        frame  runtime.Frame
        )
    pc  = rpc[0]
    if item,ok:=m.Load(pc);ok{
        frame = item.(runtime.Frame)
    }else{
        tmprpc := []uintptr{
            pc,
        }
        frame, _ = runtime.CallersFrames(tmprpc).Next()
        m.Store(pc,frame)
    }
    return frame.PC,frame.File,frame.Line,frame.PC!=0

6.simd

首先,go链接器支持simd指令,但go编译器不支持simd指令的生成。
所以在go中使用simd一般来说有三种方式:

  1. 手写汇编
  2. llvm
  3. cgo(如果用cgo的方式来调用,会受限于cgo的性能,达不到加速的目的)

目前比较流行的做法是llvm

  1. c来写simd相关的函数,然后用llvm编译成c汇编
  2. 用工具把c汇编转换成go的汇编格式,保存为.s文件
  3. 在go中调用.s里的方法,最后用go编译器编译

以下开源库用到了simd,可以参考:

  1. simdjson-go
  2. sonic
  3. sha256-simd

合理的使用simd可以充分发挥cpu特性,但是存在以下弊端:

  1. 难以维护,要么需要懂汇编的大神,要么需要引入第三方语言
  2. 跨平台支持不够,需要对不同平台汇编指令做适配
  3. 汇编代码很难调试,作为使用方来讲,完全黑盒

7.jit

go中使用jit的方式可以参考Writing a JIT compiler in Golang

目前只有在字节跳动刚开源的json解析库中发现了使用场景sonic

这种使用方式个人感觉在go中意义不大,仅供参考

总结

过早的优化是万恶之源,千万不要为了优化而优化

  1. pprof分析,竞态分析,逃逸分析,这些基础的手段是必须要学会的
  2. 常规的优化技巧是比较实用的,他们往往能解决大部分的性能问题并且足够安全。
  3. 在一些着重性能的基础库中,使用一些非常规的优化手段也是可以的,但必须要权衡利弊,不要过早放弃可读性,兼容性和稳定性。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

go性能优化 的相关文章

  • Go内存管理及性能观测工具

    内存管理 TCMalloc Golang内存分配算法主要源自Google的TCMalloc算法 TCMalloc将内存分成三层最外层Thread Cache 中间层Central Cache 最里层Page Heap Thread Cach
  • go struct{} 空结构体的特点和作用

    空结构体的特点和作用 参考代码 package main import fmt unsafe func main empStruct 空结构体的实例和作用 func empStruct 空结构体的特点 1 不占用内存 2 地址不变 var
  • 【Golang入门】Golang第一天心得

    生活所迫 入门一下Go 很奇葩的第一点 接口 package main import fmt 定义一个接口 type Shape interface Area float64 定义一个矩形类型 type Rectangle struct W
  • Go Web编程实战(6)----反射

    目录 反射 反射的3大原则 接口类型变量 转换为 反射类型对象 反射类型对象 转换为 接口类型变量 反射类型对象 修改 值必 可写的 反射 与其他语言一样 Go语言的反射同样是指 计算机程序在运行时 可以访问 检测和修改它本身状态或行为的一
  • Go项目部署及所遇问题

    小聊 本次小白给大家带来Golang项目部署操作以及个人所遇问题和解决它们的方法 依然是一边实操演示一边写文稿 如遇相似问题却存有疑惑可留言 开发环境是Window 部署环境是Linux 开发工具为GoLand 部署服务器为阿里云 1 打包
  • Go语言实现区块链与加密货币-Part3(交易优化,单机模拟多节点通信)

    交易 二 在这个系列文章的一开始 我们就提到了 区块链是一个分布式数据库 不过在之前的文章中 我们选择性地跳过了 分布式 这个部分 而是将注意力都放到了 数据库 部分 到目前为止 我们几乎已经实现了一个区块链数据库的所有元素 今天 我们将会
  • Jenkins系列:3、wsl/ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序

    Jenkins系列 3 wsl ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序 文章目录 Jenkins系列 3 wsl ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序 1 前言 2 wsl
  • go 进阶 go-zero相关: 七. 拦截器与熔断拦截器

    目录 一 拦截器的基础使用 1 服务端拦截器 2 客户端拦截器 二 拦截器底层底层执行原理 三 go zero默认添加的拦截器 客户端 1 熔断器拦截器 BreakerInterceptor 服务端 一 拦截器的基础使用 在go zero
  • go 进阶 gin实战相关: 五. gin_scaffold 企业脚手架

    目录 一 gin scaffold 企业级脚手架 二 gin scaffold 脚手架安装及使用演示 文件分层解释 开始使用 1 配置开启go mod 功能 2 下载 安装 gin scaffold 3 整合 golang common 4
  • Golang协程与通道整理

    协程goroutine 不由OS调度 而是用户层自行释放CPU 从而在执行体之间切换 Go在底层进行协助实现 涉及系统调用的地方由Go标准库协助释放CPU 总之 不通过OS进行切换 自行切换 系统运行开支大大降低 通道channel 并发编
  • Go语言里面的各种疑难杂症

    什么是闭包 闭包有什么缺陷 func AddUpper func int int var n int 10 return func x int int n n x return n func main f AddUpper fmt Prin
  • 基于Go语言实现简易Web应用

    目录 前言 Go语言特点 写在使用Go语言实现Web应用前面 创建Web服务器 声明一个结构体操作 加入中间件的使用 使用静态文件服务器 最后 前言 在编程语言中 近几年问世的几个新语言都是非常不错的 比如Go Python Rust等等
  • 为什么最近听说 Go 岗位很少很难?

    大家好 我是煎鱼 其实这个话题已经躺在我的 TODO 里很久了 近来很多社区的小伙伴都私下来交流 也有在朋友圈看到朋友吐槽 Go 上海的大会没什么人 还不如 Rust 大会 比较尴尬 今天主要是看看为什么 Go 岗位看起来近来很难的样子 也
  • 掌握 Go 语言中的循环结构:从基础到高级

    一 if else 分支结构 1 if 条件判断基本写法 package main import fmt func main score 65 if score gt 90 fmt Println A else if score gt 75
  • 【go语言开发】编写单元测试

    本文主要介绍使用go语言编写单元测试用例 首先介绍如何编写单元测试 然后介绍基本命令的使用 最后给出demo示例 文章目录 前言 命令 示例 前言 在go语言中编写单元测试时 使用说明 测试文件命名 在 Go 语言中 测试文件的命名应与被测
  • GoLong的学习之路,进阶,微服务之序列化协议,Protocol Buffers V3

    这章是接上一章 使用 RPC包 序列化中没有详细去讲 因为这一块需要看的和学习的地方很多 并且这一块是RPC中可以说是最重要的一块 也是性能的重要影响因子 今天这篇主要会讲其使用方式 文章目录 Protocol Buffers V3 背景以
  • GoLong的学习之路,进阶,Viper(yaml等配置文件的管理)

    本来有今天是继续接着上一章写微服务的 但是这几天有朋友说 再写Web框架的时候 遇到一个问题 就是很多的中间件 redis 微信 mysql mq 的配置信息写的太杂了 很不好管理 希望我能写一篇有管理配置文件的 所以这篇就放到今天写吧 微
  • go语言实现文件夹上传前后端代码案例

    go语言实现文件夹上传前后端代码案例 前端用于上传的测试界面 如果上传的文件夹有子文件要遍历子文件夹创建出子文件夹再进行拷贝 需要获取文件名和对应的路径 将文件的相对路径和文件对象添加到FormData中 这几行代码很关键 for let
  • 【go语言】error错误机制及自定义错误返回类型

    简介 Go 语言通过内置的 error 接口来处理错误 该接口定义如下 type error interface Error string 这意味着任何实现了 Error 方法的类型都可以作为错误类型 在 Go 中 通常使用 errors
  • 【go语言】读取toml文件

    一 简介 TOML 全称为Tom s Obvious Minimal Language 是一种易读的配置文件格式 旨在成为一个极简的数据序列化语言 TOML的设计原则之一是保持简洁性 易读性 同时提供足够的灵活性以满足各种应用场景 TOML

随机推荐

  • 非极大值抑制(NMS)及其变种实现

    文章目录 非极大值抑制 NMS 及其变种实现 NMS各大变种 标准NMS 局部感知NMS LNMS 倾斜NMS INMS 多边形NMS PNMS 掩膜NMS MNMS 总结 Soft NMS Motivation Method 非极大值抑制
  • 在 Webpack 中使用 art-template

    一 搭建Webpack环境 1 项目目录 2 初始化项目 npm init 3 安装 Webpack 相关依赖包 npm install save dev webpack 4 44 1 webpack cli 3 3 12 html web
  • Linux在yum时报错Could not resolve host: mirrorlist.centos.org

    1 进入修改配置文件 vi etc resolv conf 2 在配置文件里加上一句 nameserver 114 114 114 114 这是国内的dns服务器系统 还是比较好用的 谷歌的可以使用8 8 8 8 3 重启网络 servic
  • Websphere MQ 监听器

    MQ监听器管理 在本地队列管理器可以将消息发送到远程队列管理器之前 我们需要为远程队列管理器启动一个监听器 默认的 MQ 监听器端口 号是 1414 如果我们使用这个端口 那么我们在发出启动监听器 命令时就不必指定端口号 本节介绍我们如何管
  • 修改mysql中自增列的起始值

    语句如下 alter table 表名 AUTO INCREMENT 这里写起始值 例如 给user表的自增列字段设置起始值为20000 alter table user AUTO INCREMENT 20000
  • chatgpt赋能python:如何快速下载Python:指南和技巧

    如何快速下载Python 指南和技巧 如果您是一名 Python 开发人员 下载和安装一个Python解释器可能是您日常工作中最常见的任务之一 幸运的是 Python的下载和安装风格很多 这就意味着您有很多选择 本篇文章将向您介绍几个快速下
  • C++中handle的基本概念和使用

    C 中句柄 handle 的基本概念 参考博客 https blog csdn net lihuacui article details 52673398 depth 1 utm source distribute pc relevant
  • linux 杂乱汇总

    SO LINGER作用 设置函数close 关闭TCP连接时的行为 缺省close 的行为是 如果有数据残留在socket发送缓冲区中则系统将继续发送这些数据给对方 等待被确认 然后返回 利用此选项 可以将此缺省行为设置为以下两种 a 立即
  • Proteus实现555计时器模拟简易电子琴

    555计时器模拟简易电子琴 一 元器件介绍 二 原理分析 三 仿真实验 实现弹奏过程 一 元器件介绍 这里用到的元器件有 RESISTOR 电阻器 CAP 电容器 BUTTON 按钮 555 555计时器 BUZZER ACTIVE 有源蜂
  • Nacos 搭建和使用

    说明 官方文档 https nacos io zh cn docs what is nacos html 粘贴一下Nacos地图 逻辑架构及其组件介绍 服务管理 实现服务CRUD 域名CRUD 服务健康状态检查 服务权重管理等功能 配置管理
  • 4G网关8305LN远程监控西门子触摸屏SMART 700IE ZLAN8305LN应用

    1 概述 ZLAN8305LN是一款专门为工业环境设计的RS485设备数据采集器 物联网网关 他通过4G的方式传输 结合卓岚特有的P2P技术 无需构建公网服务器也可以同样随时随地采集设备的数据 本次案例主要是实现目的 对SMART700IE
  • 涉及Radio传值的写法

    博主最近在帮朋友做毕设 需要新增一个功能 功能里面有个radio列表 需要传radio元素的id值到后台 尝试了好多种写法都不行 最后用了下面的写法就好 后台顺利接收到参数 话不多说 直接上图 要如图红框里面的写法才能取到被选中的选项ID的
  • Mysql8.0的安装与配置(图文超详细)

    MySQL8 0安装配置 1 软件下载 2 软件安装 3 软件配置 4 环境变量配置 1 软件下载 链接 https pan baidu com s 1gyNz1o7SCyLIfoNcBUKRIQ 提取码 ajpl 2 软件安装 1 下载好
  • vue 收藏和取消收藏的点击事件

    描述 点击 取消收藏 图标切换 文字变为 收藏 收藏状态变为 未收藏 刷新页面 未收藏 数据不再显示 思路 通过if判断 给定这条数据一个状态 点击改变状态值 状态需要后端在接口里返回 未点击时 图标亮 状态为true 点击时 图标灰 数据
  • Qt 自定义数据类型在信号和槽中的传递

    Qt 信号和槽函数参数只能是基于 Qt 的基础类型的 比如 QString int bool 等 如果想传递自定义类型默认情况下是行不通的 下面以结构体为例 实现结构体类型数据的传递 头文件 ifndef MAINWINDOW H defi
  • 可视化分组散点图并添加分层线性回归模型的拟合曲线(使用R语言)

    可视化分组散点图并添加分层线性回归模型的拟合曲线 使用R语言 在数据分析和可视化中 经常需要绘制散点图以观察两个变量之间的关系 并使用回归模型来拟合数据 本文将介绍如何使用R语言创建分组散点图 并添加分层线性回归模型的拟合曲线 首先 我们需
  • 【操作系统】王道考研 p50-51 文件的物理结构(文件分配方式)

    文件的物理结构 上 文件的物理结构 下 知识总览 文件块 磁盘块 磁盘块的大小与内存块 页面的大小相同 内存与磁盘之间的数据交换以 块 为单位 在外存管理中 文件的逻辑地址空间被分为一个个文件块 连续分配 连续分配要求每个文件在磁盘上占有一
  • Integer的缓存机制、自动装箱拆箱

    一 什么是自动装箱拆箱 很简单 下面两句代码就可以看到装箱和拆箱过程 自动装箱 Integer total 99 自动拆箱 int totalprim total 简单一点说 装箱就是自动将基本数据类型转换为包装器类型 拆箱就是自动将包装器
  • 五行中的土在哪个方位_五行代表的方位

    五行方位 东 南 中 西 北 论 五 行 生 成 五行是构成宇宙万物的五种元素 它们是变化无穷 高深莫测的 五行的创立 準确地模拟了自然界的状态 宇宙万物的生息变化都不能离开五行 五行的顺序 一水 二火 叁木 四金 五土 这个顺序列取决于地
  • go性能优化

    常规手段 1 sync Pool 临时对象池应该是对可读性影响最小且优化效果显著的手段 最典型的就是fasthttp了 它几乎把所有的对象都用sync Pool维护 但这样的复用不一定全是合理的 比如在fasthttp中 传递上下文相关信息