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是一个高可用的分布式键值存储系统,它主要用于…...

Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...

【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...