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 的基本步骤:
-
在代码中引入
pprofimport _ "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是一个高可用的分布式键值存储系统,它主要用于…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...
