Go PGO 快速上手,性能可提高 2~4%!

大家好,我是煎鱼。

2023 年初,在 Go1.20,PGO 发布了预览版本。在本次 Go1.21 的新版本发布,修复了各种问题后,PGO 已经正式官宣生产可用。

今天这篇文章就是和大家一起跟着官方示例快速体验一下他的性能优化和使用。

温习一下 PGO

Profile-guided optimization (PGO),PGO 是计算机编程中的一种编译器优化技术,借助配置文件来引导编译,达到提高程序运行时性能的目的

翻译过来是使用配置文件引导的优化,能提供应用程序的性能。也被称为:

  • profile-directed feedback(PDF)
  • feedback-directed optimization(FDO)

该项优化是一个通用技术,不局限于某一门语言。像是我们常见很多应用都有所使用其来优化。如下几个案例:

  • Chrome 浏览器,在 64 位版本的 Chrome 中从 53 版开始启用 PGO, 32 位版在 54 版中启用。
  • AutoFDO 进行了 PGO 的优化,直接将某数据中心中的 C/C++ 程序的性能提高了 5-15%(不用改业务代码)。

Go 怎么读取 PGO

PGO 第一个版本将会先支持 pprof CPU,直接读取 pprof CPU profile 文件来完成优化。

有以下两种方式:

  • 手动指定:Go 工具链在 go build 子命令增加了 -pgo=<path> 参数,用于显式指定用于 PGO 构建的 profile 文件位置。
  • 自动指定:当 Go 工具链在主模块目录下找到 default.pgo 的配置文件时,将会自动启用 PGO。

快速 Demo

初始化应用程序

首先我们创建一个 Demo 目录,用于做一系列的实验。执行如下命令:

shell 复制代码
$ mkdir pgo-demo && cd pgo-demo

初始化模块路径和拉取程序所需的依赖:

vbnet 复制代码
$ go mod init example.com/markdown
go: creating new go.mod: module example.com/markdown

$ go get gitlab.com/golang-commonmark/markdown@bf3e522c626a

创建 main.go 文件,写入如下

go 复制代码
package main

import (
	"bytes"
	"io"
	"log"
	"net/http"
	_ "net/http/pprof"

	"gitlab.com/golang-commonmark/markdown"
)

func render(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
		return
	}

	src, err := io.ReadAll(r.Body)
	if err != nil {
		log.Printf("error reading body: %v", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	md := markdown.New(
		markdown.XHTMLOutput(true),
		markdown.Typographer(true),
		markdown.Linkify(true),
		markdown.Tables(true),
	)

	var buf bytes.Buffer
	if err := md.Render(&buf, src); err != nil {
		log.Printf("error converting markdown: %v", err)
		http.Error(w, "Malformed markdown", http.StatusBadRequest)
		return
	}

	if _, err := io.Copy(w, &buf); err != nil {
		log.Printf("error writing response: %v", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}
}

func main() {
	http.HandleFunc("/render", render)
	log.Printf("Serving on port 8080...")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

编译并运行应用程序:

go 复制代码
$ go build -o markdown.nopgo
$ ./markdown.nopgo 
2023/10/02 13:55:40 Serving on port 8080...

运行起来后进行验证,这是一个将 Markdown 转换为 HTML 的应用程序。

我们从 GitHub 上拉取一份 markdown 文件并给到该程序进行转换。如下命令:

typescript 复制代码
$ curl -o README.md -L "https://raw.githubusercontent.com/golang/go/c16c2c49e2fa98ae551fc6335215fadd62d33542/README.md"
$ curl --data-binary @README.md http://localhost:8080/render
<h1>The Go Programming Language</h1>
<p>Go is an open source programming language that makes it easy to build simple,
reliable, and efficient software.</p>
...

如果正常则说明运行没问题。

收集 PGO 所需的配置文件

一般情况下,我们可以通过生产、测试环境的 pprof 采集所需的 profile 文件,用于做 PGO 的配置文件。

但由于示例没有对应的生产环境。本次快速 Demo,Go 官方提供了一个简单的程序来快速的发压。

在确保前面小节的 pgo-demo 程序正常运行的情况下。运行如下命令,启动一个发压程序:

go 复制代码
$ go run github.com/prattmic/markdown-pgo/load@latest

收集对应的 profile 文件:

ruby 复制代码
$ curl -o cpu.pprof "http://localhost:8080/debug/pprof/profile?seconds=30"

生成了一个 cpu.pprof 文件,可以在后续使用。

应用程序使用 PGO

前面我们有提到,当模块目录下包含 default.pgo 时。Go 工具链就会自动应用 PGO

我们只需要将前面的 cpu.pprof 修改一下即可。执行如下命令:

go 复制代码
$ mv cpu.pprof default.pgo
$ go build -o markdown.withpgo

编译成功后,使用如下命令验证是否正常:

ini 复制代码
$ go version -m markdown.withpgo
markdown.withpgo: go1.21.1
	path	example.com/markdown
	mod	example.com/markdown	(devel)	
	...
	build	GOOS=darwin
	build	GOAMD64=v1
	build	-pgo=/Users/eddycjy/app/go/pgo-demo/default.pgo

可以看到最后的 build -pgo=...,代表该应用程序成功应用了我们的 default.pgo 文件(启用 PGO)。

总结

PGO 作为 Go 新版本的一个性能好帮手,在官方给出的数据中启用 PGO 后,性能能够得到一定的提升。但也会带来其他方面(CPU、大小等)的开销增加。

如本文的例子中,官方给出的数据是程序性能提升了约 24%,CPU 使用率会带来 27% 的开销增加。也可能会导致构建时长变长一些、编译后的二进制文件会稍微大一些。

面对一些场景,PGO 是一个不错的性能优化方式。但有利必有弊,就看这个应用程序的类型和综合取舍了。

文章持续更新,可以微信搜【脑子进煎鱼了】阅读,本文 GitHub github.com/eddycjy/blo... 已收录,学习 Go 语言可以看 Go 学习地图和路线,欢迎 Star 催更。

Go 图书系列

推荐阅读

相关推荐
梦想很大很大15 小时前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰20 小时前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘1 天前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤1 天前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt112 天前
AI DDD重构实践
go
Grassto3 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto5 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室6 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题6 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo
啊汉7 天前
古文观芷App搜索方案深度解析:打造极致性能的古文搜索引擎
go·软件随想