Golang高质量编程与性能调优实战

1.1 简介

高质量:编写的代码能否达到正确可靠、简洁清晰的目标

  • 各种边界条件是否考虑完备
  • 异常情况处理,稳定性保证
  • 易读易维护

编程原则

  • 简单性
    • 消除多余的重复性,以简单清晰的逻辑编写代码
    • 不理解的代码无法修复改进
  • 可读性
    • 代码是写给人看的,并不是机器
    • 编写可维护代码的第一步是确保代码可读
  • 生产力
    • 团队整体工作效率非常重要

1.2 编码规范

如何编写高质量的Go代码

1.2.1 代码格式

推荐使用gofmt自动格式化代码

主要有两种:

  • gofmt
  • goimports实际上等于gofmt加上依赖包管理,自动增删依赖包的引用、将依赖包按字母序排序并分类

1.2.2 注释

注释的作用

  • 解释代码作用:适合注释公共符号

  • 解释代码如何做的:适合注释实现过程

  • 解释代码实现的原因:适合解释代码的外部因素,提供额外的上下文

  • 解释代码什么情况会出错:适合解释代码的限制条件

  • 公共符合始终要注释:

    • 包中声明的每个公共的符号、常量、变量、函数以及结构都需要添加注释
    • 任何公共功能都必须予以注释
    • 库中的任何函数都要进行注释
    • 不需要注释实现接口的方法

1.2.3 命名规范

变量:

  • 简洁胜于冗长
  • 缩略词全大写,但是其位于变量开头且不需要导出时,使用全小写
    • 使用ServerHTTP而不是ServerHttp
    • 使用XMLHTTPRequest 或者xmlHTTPRequest
  • 变量距离被使用的地方越远,需要携带越多的上下文信息

函数:

  • 函数名不携带包名的上下文信息
  • 尽量简短
  • 名为foo的包某个函数返回类型Foo时,可以省略类型信息
  • 名为foo的包返回类型T时,可以加入类型信息

package

  • 只由小写字母组成。不包含大写字母和下划线等字符
  • 简短并包含一定的上下文信息。例如schematask
  • 不要与标准库同名。例如不要使用sync 或者strings
  • 以下规则尽量满足,以标准库包名为例
    • 不使用常用变量名作为包名。例如使用bufio 而不是 buf
    • 使用单数而不是复数。例如使用encoding 而不是encodings
    • 谨慎地使用缩写。例如使用fmt在不破坏上下文的情况下比 format更加简短

小结

  • 核心目标是降低阅读理解代码的成本

  • 重点考虑上下文信息,设计简洁清晰的名称

1.2.4 控制流程

  • 避免嵌套,保证正常流程清晰。比如如果两个分支都有return,那么第二个的else的应当省略

  • 尽量保持正常代码路径为最小缩进:优先处理错误或特殊情况,尽早返回或继续循环来减少嵌套

总结

  • 线性原理,处理逻辑尽量走直线,避免复杂的嵌套分支

  • 正常流程代码沿着屏幕向下移动

  • 提高代码的维护性和可读性

  • 故障问题大多出现在复杂的条件/循环语句里

1.2.5 错误和异常处理

简单错误

  • 简单的错误指的是仅仅出现一次的错误,而且在其他地方不需要捕获该错误
  • 优先使用errors.New来创建匿名变量直接简单的表示错误,如return errors.New("Please input a number")
  • 如果有格式化需求,使用fmt.Errorf

错误的Wrap和Unwrap

实际上是提供了error嵌套另一个error的能力

fmt.Errorf中使用%w关键字将一个错误关联到错误链中

错误判断

判断一个错误是否为特定错误,使用errors.Is

不能用==,why?

错误链上会有很多种类的错误

在错误链上获取特定种类的错误,使用errors.As

panic

不建议在业务代码里使用

如果当前 goroutine 中所有 deferred 函数都不包含 recover 就会造成整个程序崩溃

若问题可以被屏蔽或解决,建议使用error

如果程序启动阶段发生不可逆转的错误,可以在init/main函数里使用

recover

只能在被defer的函数里使用

嵌套无法生效

只在当前 goroutine 生效

defer的语句后进先出

如果需要更多的上下文信息,在log里记录当前的调用栈

总结

error 尽可能提供简明的上下文信息,方便定位问题

panic 用于真正异常的情况

recover 生效范围,在当前 goroutine 的被 defer 的函数中生效

1.3 性能优化建议

性能优化的前提是满足正确可靠、简洁清晰等质量因素

性能优化是综合评估,有时候时间效率和空间效率可能对立

1.3.1 Benchmark

如何使用

性能表现需要实际数据衡量

go test -bench=. -benchmem

image-20220511161550795.png

1.3.2 Slice

预分配内存

尽可能的在使用make初始化切片的时候提供容量信息,如data:=make([]int,0,size)

原理:在创建一个新的切片时实际上会复用原来切片的底层数组。比如append场景,当append之后的长度小于等于容量的时候,会直接利用原底层数组剩余的空间;否则,就分配一块更大的区域来容纳新的底层数组。

这样会导致的另一个问题就是大内存未释放

在已有切片基础上创建切片,不会创建新的底层数组

比如在原切片较大时,如果代码在原切片基础上新建小切片,原底层数组在内存里有引用,无法释放。这时候应该用copy来替代直接引用。

1.3.3. map

  • 不断向 map 中添加元素的操作会触发 map 的扩容
  • 根据实际需求提前预估好需要的空间
  • 提前分配好空间可以减少内存拷贝和 Rehash 的消耗

1.3.4 字符串处理

常见的字符串拼接方法

  • +
  • strings.Builder
  • bytes.Buffer

strings.Builder>bytes.Buffer >+

原理

字符串在 Go 语言中是不可变类型,占用内存大小是固定的

使用+的时候每次都会重新分配内存

strings.Builderbytes.Buffer 底层都是 []byte 数组

内存扩容策略,不需要每次拼接重新分配内存

bytes.Buffer 转化为字符串时重新申请了一块空间

strings.Builder 直接将底层的 []byte 转换成了字符串类型返回

其他优化

可以通过Grow来实现内存的预分配,提高效率

1.3.5 使用空结构体节省内存

空结构体struct{}实例不占据任何内存空间,可以作为任何场景下的占位符使用,有利于节省资源。

比如实现简单的set,就可以用map来替代

1.3.7 使用atomic包

多线程开发的时候,可以使用sync.Mutex加锁的方式,也可以用atomic.AddInt32方法。后者的效率更高。

原理

锁的实现是通过操作系统来实现,属于系统调用;而atomic是通过硬件实现,效率高。

使用场景

sync.Mutex应该用于保护一段逻辑

非数值操作可以使用atomic.Value

小结

  • 避免常见的性能陷阱可以保证大部分程序的性能

  • 针对普通应用代码,不要一味地追求程序的性能

  • 越高级的性能优化手段越容易出现问题

  • 在满足正确可靠、简洁清晰等质量要求的前提下提高程序性能

2.性能优化实战

2.1 简介

性能调优原则

  • 要依靠数据不是猜测
  • 要定位最大瓶颈而不是细枝末节
  • 不要过早优化
  • 不要过度优化

2.2 性能分析工具pprof

2.2.1 简介

可以知道应用在什么地方耗费了多少 CPU、memory 等运行指标

pprof 是用于可视化和分析性能分析数据的工具

image-20220511164704423.png

2.2.2 排查实战

使用说明

可视化工具下载地址Download | Graphviz

运行方式:先输入go build,再输入go run main.go

image-20220511165308913.png

CPU

在终端执行命令,其中seconds=10表示采取最近10s的内容

go tool pprof http://localhost:6060/debug/pprof/profile?seconds=10

然后输入top,查看 CPU 占用较高的调用

image-20220511165749995.png

image-20220511165942715.png

注释掉该部分代码然后继续进行操作

image-20220511170242116.png

heap堆内存

image-20220511170424139.png

图形化页面

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"

如果没安装graphviz的话可以继续使用终端的方式查看,下面为了方便使用图形化页面。

打开后的网站提供了以下view形式

image-20220511193849860.png

点击Graph视图得到

image-20220511194001028.png

切换到source模式查看,发现在mouse.Steal的50行,会向固定的Buffer里不断追加1M内存,直到1GB位置。

image-20220511194231787.png

注释掉这几行代码即可

=================================================================

观察到右上角有一个unknown_inuse_space,所以打开sample菜单,会发现堆内存实际上提供了4种指标,默认展示的是inuse_space视图,只展示当前持有的内存,但是如果有的内存释放,就不再展示了,切换到alloc_space指标。

image-20220511195017426.png

发现dog.run每次都会申请16MB的大小,但是分配结束后马上被GC了,所以在默认指标下看不出来。注释掉代码即可。

image-20220511195320910.png

goroutine协程

有时候goroutine泄露也会导致内存泄露,而且goroutine是很容易泄露的。

在终端输入命令

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"

点击view切换到flamegraph模式

image-20220511195917209.png

从上到下表示调用顺序,每一块表示一个函数,越长代表占用CPU的时间越长。

火焰图是动态的,支持点击块进行分析。

可以发现wolf.drink的调用为92.31%

切换到source模式,这个页面支持搜索

image-20220511200457425.png

函数每次都会发起10条无意义的协程,等待30s后才退出,就导致goroutine的泄露。如果发起的协程没有退出,同时不断有新的协程被启动,对应的内存占用持续增长,CPU调度压力也不断增大,最终进程会被系统kill掉。

注释掉这段代码

mutex锁

现在排查mutex,运行代码后输入

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"

image-20220511201151681.png

切换到souce模式,发现

image-20220511201230586.png

在这个函数里,goroutine足足等了1s才解锁,在这里阻塞住了,显然不是业务需求,注释掉

block阻塞

在程序里,除了锁的竞争会导致阻塞之外,还有很多逻辑也会导致阻塞,比如读取一个channel。

查看6060端口页面发现阻塞操作还剩2个

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"

image-20220511202337119.png

切换到source模式

image-20220511202415803.png

image-20220511202446919.png

不知道为什么跟ppt的不太一样,这里查看的时候一次就查看出了两个block

2.2.3 采用过程和原理

CPU

采样对象:函数调用和他们占用的时间

采样率:100次/秒,固定值

采样时间:从手动启动到手动结束

—共有三个相关角色:进程本身、操作系统和写缓冲。
启动采样时,进程向OS注册一个定时器,OS会每隔10ms向进程发送一个SIGPROF信号,进程接收到信号后就会对当前的调用栈进行记录。与此同时,进程会启动一个写缓冲的goroutine,它会每隔100ms从进程中读取已经记录的堆栈信息,并写入到输出流。
当采样停止时,进程向OS取消定时器,不再接收信号,写缓冲读取不到新的堆栈时,结束输出。

Heap

采样程序通过内存分配器在堆上分配和释放的内存,记录分配/释放的大小和数量

采样率:每分配512KB记录一次

采样时间:从程序运行开始到采样时

计算方式:inuse=alloc-free

Goroutine和ThreadCreate创建

Goroutine

记录所有用户发起且在运行钟的goroutine runtime.main的调用栈信息

ThreadCreate

记录程序创建的所有系统线程的信息

Block

阻塞操作:采样阻塞的次数和耗时;采样率是阻塞耗时超过阈值的才会被记录。

Mutex

采样争抢锁的次数和耗时;采样率只记录固定比例的锁操作

2.3 性能调优案例

  • 业务服务优化
  • 基础库优化
  • Go语言优化

2.3.1 业务服务优化

基础概念

服务:能够单独部署,承载一定功能的程序

依赖:服务A的功能实现依赖服务B的响应结果

调用链路:能够支持一个接口请求的相关服务集合及其相互之间的依赖关系

基础库:公用的工具包、中间件

流程

  • 建立服务性能评估手段

    • 服务性能评估的方式:单独的benchmark无法满足复杂逻辑分析,而且不同负载情况下的性能表现有差异
    • 请求流量构造:不同请求参数覆盖逻辑不同,需要尽可能的模拟线上的真实流量情况
    • 压测范围:可以是单机器压测,也可以是集群压测
    • 性能数据采集:可以是单机性能数据,也可以是集群性能数据
  • 分析性能数据,定位性能瓶颈

    • 使用基础组件不规范
    • 使用日志不规范
    • 高并发场景性能优化不足
  • 重点优化项改造

    • 性能优化的前提是保证正确性
    • 需要对比优化前后的响应数据
  • 优化效果验证

    • 重复压测验证,使用同样的数据进行压测
    • 需要结合线上的表现再进行分析改进:比如关注服务监控、逐步放量、收集性能数据

以上都是针对单个服务的优化过程

在熟悉服务器的整体部署情况后,可以针对具体的接口链路进行分析调优,只能适用于具体的场景,但是更加能够合理的利用资源

2.3.2 基础库优化

适用范围更广的是基础库的优化,大概可以结合下这篇文章几个秒杀 Go 官方库的第三方开源库 - 掘金 (juejin.cn)

2.3.3 Go语言优化

编译器和运行时优化

比如优化内存分配策略和代码编译流程,生成更高效的程序

优点为接入简单,只需要调整编译配置,而且通用性强

2.4 总结

性能调优要依靠数据而不是单纯的猜测

可以使用pprof来排查性能问题,理解基本原理

性能调优首先要保证正确性

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/298133.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

云原生学习系列之基础环境准备(单节点安装kubernetes)

一、环境要求 操作系统CentOS 7.x-86_x64 硬件配置:内存2GB或2G,CPU 2核或CPU 2核,需要在虚拟机中提前设置好,不然后续会报错 二、系统初始化 1、设置主机名 # 在master节点执行 hostnamectl set-hostname master01 2、配置主…

时间序列预测 — LSTM实现多变量多步负荷预测(Tensorflow):多输入多输出

目录 1 数据处理 1.1 导入库文件 1.2 导入数据集 ​1.3 缺失值分析 2 构造训练数据 3 LSTM模型训练 4 LSTM模型预测 4.1 分量预测 4.2 可视化 1 数据处理 1.1 导入库文件 import time import datetime import pandas as pd import numpy as np import matplotlib.p…

Kafka消息阻塞:拯救面试的八大终极解决方案!

大家好,我是小米,一个对技术充满热情的90后程序员。最近在准备社招面试的过程中,遇到了一个超级有挑战性的问题:“Kafka消息阻塞怎么解决?”今天,我就来和大家一起深入剖析这个问题,分享我在解决…

Python从入门到网络爬虫(MySQL链接)

前言 在实际数据分析和建模过程中,我们通常需要从数据库中读取数据,并将其转化为 Pandas dataframe 对象进行进一步处理。而 MySQL 数据库是最常用的关系型数据库之一,因此在 Python 中如何连接 MySQL 数据库并查询数据成为了一个重要的问题…

【MySQL四大引擎,数据库管理,数据表管理,数据库账号管理】

一. MySQL四大引擎 查看存储引擎 SHOW ENGINES support 字段说明 defaulti的为默认的引擎 为YES表示可以使用 为NO表示不能使用 四大引擎 InnoDB InnoDB表类型可以看作是对MyISAM的进一步更新产品,它提供了事务、行级锁机制和外键约束的功能,也是目前…

Python中的cls语法

在Python中,cls 是一个用于指代类本身的约定性名称,通常用作类方法(class method)中的第一个参数。cls 类似于 self,它是对类的引用,而不是对实例的引用。cls 通常在类方法中用于访问类级别的属性和方法。举…

智能门锁人脸识别好用监控不好用是怎么回事?

智能门锁的人脸识别和监控所用的主要硬件都是摄像头,如果二个功能都共用同一摄像头的话,所拍出来的图像清晰度应该是一样的,但有些智能锁可能并非如此,况且它们是两个不同的功能,所以成像的清晰度可能并不一样&#xf…

栅极驱动芯片三种隔离技术

栅极驱动芯片三种隔离技术 1.栅极驱动器概述2.隔离栅极驱动芯片2.1隔离驱动器重要指标 3.三种常见隔离技术3.1光隔离3.2变压器隔离/磁隔3.3电容隔离 4.三种隔离器性能对比 1.栅极驱动器概述 栅极驱动器,在任何功率水平为任何应用高效可靠地驱动任何功率开关。 比如M…

虾皮长尾词工具:如何使用关键词工具优化Shopee产品的长尾关键词

在Shopee(虾皮)平台上,卖家们都希望能够吸引更多的潜在买家,提高产品的曝光率和转化率。而要实现这一目标,了解和使用长尾关键词是非常重要的。本文将介绍长尾关键词的定义、重要性以及如何使用关键词工具来优化Shopee…

Spring Data JPA入门到放弃

参考文档:SpringData JPA:一文带你搞懂 - 知乎 (zhihu.com) 一、 前言 1.1 概述 Java持久化技术是Java开发中的重要组成部分,它主要用于将对象数据持久化到数据库中,以及从数据库中查询和恢复对象数据。在Java持久化技术领域&a…

leetcode经典【双指针】例题

删除有序数组中的重复项: https://leetcode.cn/problems/remove-duplicates-from-sorted-array/ 解题思路: 首先注意数组是有序的,那么重复的元素一定会相邻。 注: 要求删除重复元素,实际上就是将不重复的元素移到数组的左侧。 考…

18.标题统计

题目 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);String str sc.nextLine();int res 0;for(int i0;i<str.length();i) {char c str.charAt(i);if(c! && c!\n) {res;}}System.o…

BUUCTF--pwnable_start1

查看保护&#xff1a; 32位程序保护全没开&#xff0c;黑盒测试下效果&#xff1a; 存在栈溢出&#xff0c;那么这题的想法就是直接ret2shellcode了。IDA中看看具体流程&#xff1a; 出奇的少&#xff0c;这题不能看反汇编的代码&#xff0c;直接去看汇编&#xff1a; 主要就2个…

sql——窗口范围之partition by 与 order by

partition by 关键字 partition by 在开窗函数中&#xff0c;常用于表示某个分区&#xff0c;规则了数据的范围 order by 关键字 order by 常用于对分区内的数据进行排序&#xff0c;常见的情况下&#xff0c;order by还能规定sql语句的影响范围。 rows between unbounded …

kannegiesser触摸屏维修CTT-11 4PP420.1043-K37

贝加莱触摸屏维修4PP420.1043-K37 kannegiesser工控机触摸屏维修CTT-11 工控机触摸屏维修常见故障现象 1、工控机开机有显示&#xff0c;但是屏幕很暗&#xff0c;用调亮度功能键调试无任何变化&#xff1b; 2、工控机开机触摸屏白屏或花屏&#xff0c;但是外接显示器正常&a…

机器学习(四) -- 模型评估(3)

系列文章目录 机器学习&#xff08;一&#xff09; -- 概述 机器学习&#xff08;二&#xff09; -- 数据预处理&#xff08;1-3&#xff09; 机器学习&#xff08;三&#xff09; -- 特征工程&#xff08;1-2&#xff09; 机器学习&#xff08;四&#xff09; -- 模型评估…

【JAVA】volatile 关键字的作用

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 volatile 的作用&#xff1a; 结语 我的其他博客 前言 在多线程编程中&#xff0c;保障数据的一致性和线程之间的可见性是…

优化|PLSA理论与实践

PLSA又称为概率潜在语义分析&#xff0c;是一种利用概率生成模型对文本集合进行话题分析的无监督学习方法。该模型最大的特点是加入了主题这一隐变量&#xff0c;文本生成主题&#xff0c;主题生成单词&#xff0c;从而得到单词-文本共现矩阵。本文将对包含物理学、计算机科学、…

嵌入式(五)通信协议 | 串行异步同步 UART SPI I2C 全解析

文章目录 0 串口通信协议1 通用异步收发传输器 UART1.1 串口配置1.2 串口初始化1.3 串口发送和接收方式1.3.1 轮询方式发送1.3.2 中断方式发送1.3.3 查询方式接收1.3.4 中断方式接收 2 串行外设接口 SPI2.1 标准的四线SPI接口2.2 SPI的四种模式2.3 配置2.4 发送和接收Master向S…

[python]gym安装报错ERROR: Failed building wheel for box2d-py

报错截图&#xff1a; box2d是一个游戏领域的2D图形C引擎&#xff0c;用来模拟2D刚体物体运动和碰撞。 swig是一个将c/c代码封装为Python库的工具&#xff08;是Python调用c/c库的一种常见手段&#xff09;&#xff0c;所以在运行时box2d会依赖到swig。而swig并不是一个python库…