引言和介绍
优化 Go 程序的性能和资源占用是非常必要的,通过优化可以:
-
减少程序的执行时间,提高整体的吞吐量和响应性能,使用户获得更好的体验,
-
可以减少程序对系统资源的占用,如内存、CPU、网络等。减少硬件成本。
-
同时性能优化是提升产品和服务竞争力的一种重要手段。
使用合适的数据结构和算法,并发处理,使用高效的库和工具,基准测试和持续优化,减少系统调用是进行go程序优化的一般途径。
实例演习
package main
import (
"fmt"
"log"
"net/http"
)
// 定义处理程序函数(Handler Function)
func handler(w http.ResponseWriter, r *http.Request) {
// 向 HTTP 响应中写入 "Hello, World!" 字符串
fmt.Fprintf(w, "Hello, World!")
}
func main() {
// 注册处理程序函数,当收到根路径请求时会执行该函数
http.HandleFunc("/", handler)
// 启动 HTTP 服务器,并监听 8080 端口
log.Println("启动 HTTP 服务器...")
err := http.ListenAndServe(":8080", nil)
if err != nil {
// 如果启动失败,则输出错误日志并退出程序
log.Fatal("HTTP 服务器启动失败:", err)
}
}
代码分析
我们使用 Go 标准库创建了一个简单的 HTTP 服务器。在主函数中监听本地的 8080 端口,当有请求访问根路径时,服务器会返回 "Hello, World!" 的响应。
如果启动过程中出现错误,则使用 log.Fatal 函数记录错误信息,并终止程序的执行。
优化策略
并发处理:
将处理函数包装在协程中,允许并发处理多个请求。
// 定义处理程序函数(Handler Function)
func handler(w http.ResponseWriter, r *http.Request) {
// 设置响应头的 Content-Type 为 text/plain
w.Header().Set("Content-Type", "text/plain")
// 创建一个用于通信的字符串类型的通道
ch := make(chan string)
// 启动一个 goroutine 来异步执行任务,并将结果发送到通道
go func() {
ch <- "Hello, World!" // 将 "Hello, World!" 传递给通道
}()
response := <-ch // 从通道中接收响应
fmt.Fprint(w, response) // 将响应写入 HTTP 响应中
}
在处理函数 handler 中,我们首先通过 w.Header().Set() 设置响应头部的 Content-Type。然后创建一个通道 ch 用于接收处理结果。
接着,在协程中生成响应字符串并发送到通道中。在主协程中,我们从通道 ch 中接收处理结果,并将结果写入 http.ResponseWriter 中。
这样就能够实现在不阻塞主协程的情况下并发处理请求,并将处理结果正确地写入到 http.ResponseWriter 中。
连接复用:
使用 Keep-Alive 特性启用连接复用,避免频繁建立和断开连接。
func main() {
// 创建一个 HTTP 服务器实例
server := &http.Server{
Addr: ":8080", // 监听地址和端口号
Handler: http.HandlerFunc(handler), // 设置处理程序函数
}
server.SetKeepAlivesEnabled(true) // 启用 Keep-Alive 连接
log.Println("启动 HTTP 服务器...")
// 启动并监听 HTTP 服务器
err := server.ListenAndServe()
if err != nil {
log.Fatal("HTTP 服务器启动失败:", err)
}
}
通过调用 server.SetKeepAlivesEnabled(true) 方法,告诉了 HTTP 服务器要启用连接复用。这将使得服务器在处理完一个请求后保持连接打开,以便可以在后续请求中复用这个连接。然后,通过调用 server.ListenAndServe() 方法来启动 HTTP 服务器,并监听在地址 :8080 上。
当有新的请求到达时,HTTP 服务器会使用指定的 handler 函数来处理该请求。
基准测试
基准测试是用于评估代码性能的一种方法,它可以比较不同算法或数据结构的效率、优化代码性能等。下面我们通过实例初步了解其具体的使用。
import (
"testing"
"io"
)
基准测试需要引入testing包,
视演示代码情况而定引入io包
func BenchmarkHTTPServer(b *testing.B) {
// 创建一个测试用的 HTTP 服务器实例
server := httptest.NewServer(http.HandlerFunc(handler))
defer server.Close()
// 使用测试服务器创建 HTTP 客户端
client := server.Client()
// 重置计时器并循环执行测试
b.ResetTimer()
for i := 0; i < b.N; i++ {
// 发送 GET 请求并接收响应
resp, err := client.Get(server.URL)
if err != nil {
log.Fatal("请求失败:", err)
}
// 将响应体内容读取并丢弃
_, err = io.Copy(io.Discard, resp.Body)
if err != nil {
log.Fatal("响应解析失败:", err)
}
// 关闭响应体
resp.Body.Close()
}
}
这里我们定义一个基准测试函数BenchmarkHTTPServer,该函数通过httptest.NewServer创建一个模拟的 HTTP 服务器,并使用基准测试循环模拟多个并发请求。在基准测试函数中,我们使用client.Get发送 GET 请求并测量性能。
// 运行基准测试
result := testing.Benchmark(BenchmarkHTTPServer)
fmt.Println(result)
在main函数中,我们需要运行基准测试函数,并将结果打印出来。
通过在优化前后的代码中添加以上代码后,我们便可以运行程序进行性能测试。
如图可见:
优化前,执行了 29929 次基准测试迭代,平均操作时间为 43026 纳秒/操作,每个操作约耗时 43.026 微秒。
优化后,执行了 30134 次基准测试迭代,平均操作时间为 38046 纳秒/操作,每个操作约耗时 38.046 微秒。
补充:pprof
go语言中还提供了pprof进行性能调试 具体内容可参照下篇。
实用go pprof使用指南 - 知乎 (zhihu.com)
mux := http.NewServeMux()
// 注册 pprof 的路由处理器
mux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
除了该片中所提到的网页导出数据,亦可以使用以下代码加到程序开始运行处。运行之后通过访问http://localhost:8080/debug/pprof/得到如下界面进行数据采集。
(该网页访问localhost:8080,预计需要安装TomCat)
总结
Go语言提供了便利的工具去分析程序的性能,以此我们再从程序的应用场景对程序进行适当的优化改进。熟练的运用这些工具并熟悉语法对症下药确实很重要,还需要多积累项目经验。
(好事多磨,不断求解总算解决。)