15分钟学 Go 第 35 天:Go的性能调优 (7000字详细教程)
第35天:Go的性能调优
目标:理解Go语言中基本的性能优化,学习如何分析和提高Go程序的执行效率。
一、性能调优概述
性能调优是软件开发中的一个重要环节,它可以确保程序在资源有限的环境下高效运行。Go语言天生具备高效的性能表现,但即使如此,也有很多细节可以通过优化进一步提升程序的执行速度和资源使用效率。
在本节中,我们将重点介绍以下几种性能调优的策略:
- 内存优化:减少内存分配,优化GC(垃圾回收)。
- CPU优化:通过减少不必要的计算、并发调度和锁竞争提升CPU效率。
- I/O优化:提高文件、网络等I/O操作的性能。
二、性能分析工具
在进行性能优化之前,首先需要通过分析工具发现性能瓶颈。Go 语言提供了一些内置工具,帮助开发者分析和优化程序的性能。
1. pprof
性能分析工具
Go 标准库中的 pprof
包可以生成并分析 CPU、内存等的性能数据。你可以通过命令行或 Web 界面分析这些数据。
使用 pprof
的基本步骤:
-
在代码中引入
pprof
import _ "net/http/pprof"
-
启动性能分析
在代码运行时,将性能数据暴露在 HTTP 服务器上:go func() {log.Println(http.ListenAndServe("localhost:6060", nil)) }()
-
访问性能数据
通过localhost:6060/debug/pprof/
获取性能数据,包括 CPU 使用、内存分配等。 -
生成性能报告
使用go tool pprof
来生成性能分析报告。
常见 pprof
URL:
localhost:6060/debug/pprof/goroutine
:查看当前 goroutine 数量。localhost:6060/debug/pprof/heap
:查看内存堆使用情况。localhost:6060/debug/pprof/profile?seconds=30
:生成 CPU 分析报告,记录 30 秒的数据。
2. trace
跟踪工具
Go trace
工具用于跟踪程序的执行时间、goroutine 的创建和调度、系统调用等情况,有助于调优并发和 I/O 性能。
使用 trace
的步骤:
-
启动跟踪并生成日志文件:
go test -trace trace.out
-
分析
trace
文件:go tool trace trace.out
三、内存优化
1. 减少内存分配
内存分配是影响性能的一个主要因素。频繁的小内存分配会导致 GC(垃圾回收)压力增加,从而影响程序性能。因此,减少不必要的内存分配是提高性能的关键之一。
示例:优化内存分配
不优化的代码:
package mainfunc createSlice() []int {s := make([]int, 0)for i := 0; i < 100000; i++ {s = append(s, i)}return s
}func main() {_ = createSlice()
}
上面的代码每次 append
都可能导致重新分配内存。可以通过预先指定容量减少多次分配内存的操作。
优化后的代码:
package mainfunc createSlice() []int {s := make([]int, 0, 100000) // 预先分配足够的容量for i := 0; i < 100000; i++ {s = append(s, i)}return s
}func main() {_ = createSlice()
}
2. 使用对象池 (sync.Pool
)
Go 语言的 sync.Pool
是一个对象池,用于缓存和重用临时对象,从而减少内存分配和GC压力。适合用于短期生命周期对象的优化。
示例:使用 sync.Pool
package mainimport ("fmt""sync"
)func main() {var pool = sync.Pool{New: func() interface{} {return new(int) // 创建一个新的对象},}// 从对象池获取对象obj := pool.Get().(*int)*obj = 100fmt.Println(*obj)// 将对象放回池中pool.Put(obj)// 重新从池中获取对象newObj := pool.Get().(*int)fmt.Println(*newObj) // 对象被重用
}
3. 减少逃逸分析
Go 编译器使用逃逸分析来决定变量是分配在栈上还是堆上。分配在堆上的对象会增加 GC 压力,因此减少逃逸的对象是优化的重点。
示例:逃逸分析
package mainimport "fmt"func main() {x := 10fmt.Println(&x) // x 逃逸到堆上
}
优化后,尽量减少变量的指针传递:
package mainimport "fmt"func main() {x := 10fmt.Println(x) // x 保留在栈上
}
四、CPU优化
1. 并发优化
Go 语言的并发模型基于 goroutine 和通道。虽然 goroutine 是轻量级的,但它们的数量和调度方式会直接影响程序的 CPU 使用情况。
示例:不合理的并发
package mainimport "sync"func main() {var wg sync.WaitGroupfor i := 0; i < 100000; i++ {wg.Add(1)go func() {defer wg.Done()}()}wg.Wait()
}
这种写法可能创建大量的 goroutine,造成不必要的上下文切换。可以通过限制 goroutine 的数量来优化。
示例:优化并发
package mainimport ("sync"
)func main() {var wg sync.WaitGrouppoolSize := 100 // 控制并发数量sem := make(chan struct{}, poolSize)for i := 0; i < 100000; i++ {wg.Add(1)sem <- struct{}{}go func() {defer wg.Done()<-sem}()}wg.Wait()
}
2. 减少锁竞争
当多个 goroutine 同时访问共享资源时,锁竞争会导致性能下降。应尽量减少锁的使用或缩小锁的粒度。
示例:锁竞争优化
package mainimport ("sync""time"
)var mu sync.Mutexfunc criticalSection() {mu.Lock()time.Sleep(1 * time.Second)mu.Unlock()
}func main() {for i := 0; i < 10; i++ {go criticalSection()}time.Sleep(2 * time.Second)
}
优化策略:缩小锁的作用范围或使用 sync.RWMutex
读写锁。
package mainimport ("sync""time"
)var mu sync.RWMutexfunc readSection() {mu.RLock()time.Sleep(1 * time.Second)mu.RUnlock()
}func writeSection() {mu.Lock()time.Sleep(1 * time.Second)mu.Unlock()
}func main() {for i := 0; i < 10; i++ {go readSection()}time.Sleep(2 * time.Second)
}
五、I/O优化
1. 缓存读写操作
频繁的 I/O 操作(文件读写、网络请求等)是性能瓶颈之一。通过缓存减少 I/O 的频率,可以显著提高程序性能。
示例:优化文件读取
不使用缓存的文件读取:
package mainimport ("io/ioutil""log"
)func main() {content, err := ioutil.ReadFile("largefile.txt")if err != nil {log.Fatal(err)}log.Println(len(content))
}
优化后的代码,使用 bufio
缓存读写:
package mainimport ("bufio""log""os"
)func main() {file, err := os.Open("largefile.txt")if err != nil {log.Fatal(err)}defer file.Close()reader := bufio.NewReader(file)buffer := make([]byte, 1024)for {_, err := reader.Read(buffer)if err != nil {break}}
}
2. 异步I/O
在进行网络请求、数据库操作等可能涉及延迟的I/O操作时,使用异步I/O可以避免阻塞主线程,提升系统的吞吐量。Go语言的goroutine天生适合处理这种并发场景,通过使用goroutine和channel的组合,可以实现高效的异步I/O处理。
示例:同步I/O vs 异步I/O
同步I/O(阻塞):
package mainimport ("fmt""net/http""time"
)func fetchData(url string) {start := time.Now()resp, err := http.Get(url)if err != nil {fmt.Println("Error:", err)return}defer resp.Body.Close()fmt.Println("Fetched data from:", url, "in", time.Since(start))
}func main() {fetchData("https://example.com")fetchData("https://golang.org")
}
在同步I/O操作中,第二次请求必须等到第一次请求结束后才能开始。这样可能会延长程序的总运行时间。
异步I/O(非阻塞):
package mainimport ("fmt""net/http""time"
)func fetchData(url string, ch chan<- string) {start := time.Now()resp, err := http.Get(url)if err != nil {ch <- fmt.Sprintf("Error fetching %s: %v", url, err)return}defer resp.Body.Close()ch <- fmt.Sprintf("Fetched data from %s in %v", url, time.Since(start))
}func main() {ch := make(chan string)go fetchData("https://example.com", ch)go fetchData("https://golang.org", ch)fmt.Println(<-ch)fmt.Println(<-ch)
}
在异步I/O版本中,我们使用goroutine并发地处理多个请求,从而显著减少了总执行时间。
六、性能优化的流程图
下图展示了Go性能优化的步骤,从初步的性能分析,到针对性的优化策略:
+----------------------------------+
| 性能分析(CPU/内存/I/O) |
| 使用pprof或trace工具进行分析 |
+----------------------------------+|v
+----------------------------------+
| 找到性能瓶颈(热点部分) |
| 分析代码中的资源消耗点 |
+----------------------------------+|v
+----------------------------------+
| 选择合适的优化策略(CPU/内存/I/O)|
| 比如:减少内存分配、优化并发模型 |
+----------------------------------+|v
+----------------------------------+
| 进行代码优化 |
| 重构代码、减少锁竞争或I/O延迟 |
+----------------------------------+|v
+----------------------------------+
| 重新测试程序性能 |
| 确保优化后的性能有提升 |
+----------------------------------+
通过不断迭代和分析,开发者可以逐步提高程序的性能。
七、常见的性能优化策略对比
下表总结了不同场景下的常用性能优化策略,以及它们适用的情况。
优化策略 | 场景 | 优点 | 缺点 |
---|---|---|---|
预先分配内存 | 大量动态增长的slice | 减少内存分配次数,降低GC压力 | 需要准确估计容量,可能会导致内存浪费 |
使用 sync.Pool | 临时对象的频繁创建 | 重用对象,减少垃圾回收的开销 | 适用范围有限,适合短期对象 |
并发控制 | 高并发场景 | 控制goroutine数量,减少上下文切换 | 需要手动设计并发模型 |
缓存I/O | 大量文件或网络请求 | 减少I/O次数,提升吞吐量 | 增加了缓存管理的复杂度 |
异步I/O | 网络、数据库操作 | 非阻塞处理,提升响应速度 | 需要处理异步回调的复杂性 |
缩小锁的粒度 | 高锁竞争场景 | 减少锁的持有时间,降低锁竞争 | 可能会导致更多锁,增加代码复杂度 |
减少指针逃逸 | 大量堆内存分配 | 降低GC压力,提升内存访问效率 | 需要手动调整变量生命周期 |
八、性能优化中的常见陷阱
在性能优化的过程中,有几个常见的陷阱需要避免:
-
过度优化
不要为优化而优化。在性能调优前,先确保程序的正确性和可读性,只有当性能瓶颈确实对系统造成影响时,才进行优化。微小的性能提升往往并不值得复杂化代码。 -
忽视分析工具
使用工具进行性能分析是至关重要的。不要凭借直觉来判断瓶颈位置,借助pprof
或trace
等工具来验证性能问题。 -
忽略GC和内存泄漏
Go 的垃圾回收机制很强大,但如果不加控制,频繁的内存分配和回收可能会影响程序的性能。通过go tool pprof
分析GC的开销,避免内存泄漏和过多的对象逃逸到堆上。 -
并发过度
虽然Go的goroutine是轻量级的,但并不意味着可以肆意创建成千上万的goroutine。在并发场景中,合理控制goroutine的数量,防止过多的上下文切换带来性能问题。
九、代码优化示例
我们通过一个综合示例,展示如何从性能分析到优化实现。
示例:简单HTTP服务器的性能优化
初始版本:
package mainimport ("fmt""net/http"
)func handler(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}func main() {http.HandleFunc("/", handler)http.ListenAndServe(":8080", nil)
}
这是一个简单的HTTP服务器,每次请求都会处理并返回一个Hello消息。在高并发场景下,它的性能可能表现不佳。
性能优化步骤:
-
性能分析:
通过pprof
工具,发现瓶颈主要在于请求处理的性能。 -
并发优化:
通过引入sync.Pool
缓存响应对象,减少每次请求的内存分配开销。 -
I/O优化:
使用bufio
进行缓冲写入,减少I/O操作次数。
优化后的版本:
package mainimport ("bufio""fmt""net/http""sync"
)var bufPool = sync.Pool{New: func() interface{} {return bufio.NewWriter(nil)},
}func handler(w http.ResponseWriter, r *http.Request) {bw := bufPool.Get().(*bufio.Writer)bw.Reset(w)fmt.Fprintf(bw, "Hello, %s!", r.URL.Path[1:])bw.Flush()bufPool.Put(bw)
}func main() {http.HandleFunc("/", handler)http.ListenAndServe(":8080", nil)
}
通过这几步优化,HTTP服务器的内存分配减少了,I/O操作得到了优化,从而提升了系统的整体吞吐量。
十、总结
通过今天的学习,你应该了解了Go语言中基本的性能优化策略。性能调优不仅是为了提升程序的运行速度,更是为了合理分配系统资源。记住,在进行优化前,先使用分析工具找到性能瓶颈,再针对性地进行优化。同时,务必保持代码的可读性,避免过度优化。
关键点回顾:
- 使用
pprof
、trace
等工具分析性能瓶颈。 - 通过减少内存分配、优化并发和I/O操作来提升性能。
- 保持代码简单可维护,避免过度优化。
怎么样今天的内容还满意吗?再次感谢观众老爷的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!
相关文章:

15分钟学 Go 第 35 天:Go的性能调优 (7000字详细教程)
第35天:Go的性能调优 目标:理解Go语言中基本的性能优化,学习如何分析和提高Go程序的执行效率。 一、性能调优概述 性能调优是软件开发中的一个重要环节,它可以确保程序在资源有限的环境下高效运行。Go语言天生具备高效的性能表现…...

6、显卡品牌分类介绍:技嘉 - 计算机硬件品牌系列文章
技嘉科技是一家以主板、显卡在业界缔造无以撼动的地位的科技公司,其核心理念是「技术创新、质量稳定」的高标准。技嘉专注于关键技术研发,其经营范围涵盖家用、商用、电竞等多元科技领域。通过应用突破性的专利技术,技…...

Redis数据类型——针对实习面试
目录 Redis数据类型Redis常用的数据类型有哪些?String类型可以用于哪些场景?Set类型可以用于哪些场景?Bitmaps类型可以用于哪些场景?HyperLogLog类型可以用于哪些场景?Hash类型与Set类型有什么区别?Hash类型…...

roberta融合模型创新中文新闻文本标题分类
项目源码获取方式见文章末尾! 600多个深度学习项目资料,快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【基于CNN-RNN的影像报告生成】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模型实现…...

《密码系统设计》实验二 4-6学时
文章目录 《密码系统设计》实验实验项目实验二 密码算法实现4-6 学时实践要求(30 分)1. 定义宏2. 使用特定的源文件3. 编译MIRACL库4. 配置KCM和Comba方法5. 编译和运行MEX工具6. 使用config.c工具总结1. 准备环境2. 下载和解压MIRACL库3. 定义宏4. 使用…...

Zypher Network:全栈式 Web3 游戏引擎,服务器抽象叙事的引领者
近期,《黑神话:悟空》的爆火不仅让 AAA 游戏重回焦点,也引发了玩家与开发者的热议。Web2 游戏的持续成功导致部分 Web3 玩家们的倒戈,对比之下 Web3 游戏存在生命周期短且商业模式难以明确的问题,尤其在当前加密市场环…...

2025生物发酵展(济南)为生物制造产业注入新活力共谱行业新篇章
2025第十四届国际生物发酵展将于3月3-5日济南盛大举办!产业链逐步完整,展会面积再创历史新高,展览面积较上届增涨至60000平方米,专业观众40000,品牌展商800,同期活动会议增加至50场,展会同期将举…...

git入门教程14:Git与其他工具的集成
一、Git与代码托管平台的集成 GitHub 集成方式: 在GitHub上创建或克隆仓库。在本地使用Git命令进行代码提交和推送(如git push)。GitHub提供Web界面进行代码浏览、协作和持续集成配置。 特点: 支持Pull Request,便于代…...

在Zetero中调用腾讯云API的输入密钥的问题
也是使用了Translate插件了,但是需要调用腾讯云翻译,一直没成功。 第一步就是,按照这上面方法做:百度、阿里、腾讯、有道各平台翻译API申请教程 之后就是:Zotero PDF translat翻译:申请腾讯翻译接口 主要是…...

【AD】1-8 AD24软件工程创建
1.点击文件,新建项目 2.如图进行设置工程名称和文件路径 3.创建原理图库及原理图,并保存 4.新建PCB库及PCB,并保存 5.单击右键工程保存 注意:先新建工程,在新建文件...

RT-Thread学习
文章目录 前言一、rtt的启动流程二、移植工作总结 前言 RT-Thread学习,这里记录对bsp的移植 一、rtt的启动流程 RT-Thread 支持多种平台和多种编译器,而 rtthread_startup() 函数是 RT-Thread 规定的统一启动入口。一般执行顺序是:系统先从…...

20241102在荣品PRO-RK3566开发板使用荣品预编译的buildroot通过iperf2测试AP6256的WIFI网速
20241102在荣品PRO-RK3566开发板使用荣品预编译的buildroot通过iperf2测试AP6256的WIFI网速 2024/11/2 14:18 客户端:荣耀手机HONOR 70【iPerf2 for Android】 服务器端:荣品PRO-RK3566开发板 预编译固件:update-pro-rk3566-buildroot-hdmi-2…...

网络模型——二层转发原理
网课地址:网络模型_二层转发原理(三)_哔哩哔哩_bilibili 一、路由交换 网络:用来信息通信,信息共享的平台。 网络节点(交换机,路由器,防火墙,AP)介质&#…...

【编程技巧】C++如何使用std::map管理std::function函数指针
一、问题背景 开发过程中遇到了需要根据const字符串调用不同函数的要求。在开发过程中为了快速实现功能,实际使用了if else等判断实现了不同函数的调用,徒增了不少代码行数。 明知道可以采用map管理函数指针,但是没有具体实现过,…...

导航栏小案例
实现类似于这样的效果 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>导航栏</title><style>*{margin: 0;padding: 0;}.div1{width: 100%;height: 60px;/* border: 1px solid blue; */background-color:rgb(…...

MyBatis一文入门精通,面试题(含答案)
一、MyBatis详细介绍 MyBatis 是一个流行的 Java 持久层框架,主要用于简化 SQL 数据库操作。它的设计初衷是通过 XML 或注解的方式配置和执行 SQL 语句,使得数据库操作更加灵活、方便和高效。相比于传统的 JDBC,MyBatis 提供了一些关键优势&…...

Ubuntu18.04服务器非root用户在虚拟环境下的python版本设定
最近需要跑一个python3.9.16版本的代码,Ubuntu18.04服务器上是上次博客中已经定死的python3.8.0版本 需要创建一个虚拟环境,并且在虚拟环境中配置python3.9.16版本 只需要创建一个虚拟环境 conda create -n yyy python3.9.16yyy是你的虚拟环境名字 创建…...

CodeS:构建用于文本到 SQL 的开源语言模型
发布于:2024 年 10 月 29 日 #RAG #Text2 SQL #NL2 SQL 语言模型在将自然语言问题转换为 SQL 查询(文本到 SQL )的任务中显示出良好的性能。然而,大多数最先进的 (SOTA) 方法都依赖于强大但闭源的大型语言…...

HTML 基础概念:什么是 HTML ? HTML 的构成 与 HTML 基本文档结构
文章目录 什么是 HTML ?HTML 的构成 ?什么是 HTML 元素?HTML 元素的组成部分HTML 元素的特点 HTML 基本文档结构如何打开新建的 HTML 文件代码查看 什么是 HTML ? HTML(超文本标记语言,HyperText Markup L…...

18 Docker容器集群网络架构:一、etcd 概述
文章目录 Docker容器集群网络架构:一、etcd概述1.1 etcd 的基本概念和特点1.1.1 定义1.1.2 特点1.2 etcd 在 Docker 集群网络中的作用1.3 etcd 集群的架构和原理1.3.1 架构1.3.2 原理Docker容器集群网络架构:一、etcd概述 etcd是一个高可用的分布式键值存储系统,它主要用于…...

R语言贝叶斯分层、层次(Hierarchical Bayesian)模型房价数据空间分析
原文链接:https://tecdat.cn/?p38077 本文主要探讨了贝叶斯分层模型在分析区域数据方面的应用,以房价数据为例,详细阐述了如何帮助客户利用R进行模型拟合、分析及结果解读,展示了该方法在处理空间相关数据时的灵活性和有效性。&a…...

SpringBoot 在初始化加载无法使用@Value的时候读取配置文件教程
怀旧网个人博客地址:怀旧网,博客详情:SpringBoot 在初始化加载无法使用Value的时候读取配置文件教程 读取数据库数据案例 // 创建YamlPropertiesFactoryBean对象 YamlPropertiesFactoryBean factory new YamlPropertiesFactoryBean(); // …...

基于MATLAB的身份证号码识别系统
课题介绍 本课题为基于连通域分割和模板匹配的二代居民身份证号码识别系统,带有一个GUI人机交互界面。可以识别数十张身份证图片。 首先从身份证图像上获取0~9和X共十一个号码字符的样本图像作为后续识别的字符库样本,其次将待测身份证图像…...

【人工智能-初级】练习题:matplotlib基础练习30例
练习 1: 画折线图 import matplotlib.pyplot as plt x = [1, 2, 3, 4, 5] y = [10, 20, 25, 30, 40] 使用 plt.plot() 画出折线图,适用于连续数据的可视化 plt.plot(x, y) plt.xlabel(‘X 轴’) plt.ylabel(‘Y 轴’) plt.title(‘简单折线图’) plt.show() 练习 2: 画散…...

【002】基于SpringBoot+thymeleaf实现的蓝天幼儿园管理系统
基于SpringBootthymeleaf实现的蓝天幼儿园管理系统 文章目录 系统说明技术选型成果展示账号地址及其他说明源码获取 系统说明 基于SpringBootthymeleaf实现的蓝天幼儿园管理系统是为幼儿园提供的一套管理平台,可以提高幼儿园信息管理的准确性,系统将信息…...

nvm详解
本文借鉴转载于 nvm文档手册 文章目录 1.nvm是什么?2.nvm安装2.1 window上安装下载链接安装步骤 2.2 Mac上安装使用homebrew 安装 nvm 3.nvm使用指令 1.nvm是什么? nvm(Node Version Manager)是一个用于管理和切换不同版本 Node.…...

Lucene的概述与应用场景(1)
文章目录 第1章 Lucene概述1.1 搜索的实现方案1.1.1 传统实现方案1.1.2 Lucene实现方案 1.2 数据查询方法1.1.1 顺序扫描法1.1.2 倒排索引法 1.3 Lucene相关概念1.3.1 文档对象1.3.2 域对象1)分词2)索引3)存储 1.3.3 常用的Field种类 1.4 分词…...

11.3笔记
在C#中,静态类和普通类(实例类)有一些关键的区别: 实例化: 普通类:可以被实例化,即创建对象。每个对象都有自己的状态和方法。静态类:不能被实例化,它们不包含构造函数&a…...
数据结构之线段树
线段树 线段树(Segment Tree)是一种高效的数据结构,广泛应用于计算机科学和算法中,特别是在处理区间查询和更新问题时表现出色。以下是对线段树的详细解释: 一、基本概念 线段树是一种二叉搜索树,是算法竞…...

vue 快速入门
文章目录 一、插值表达式 {{}}二、Vue 指令2.1 v-text 和 v-html:2.2 v-if 和 v-show:2.3 v-on:2.4 v-bind 和 v-model:2.5 v-for: 三、生命周期四、Vue 组件库 Element五、Vue 路由 本文章适用于后端人员,…...