前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >亲测体验Go语言PGO

亲测体验Go语言PGO

作者头像
fliter
发布2023-12-06 17:23:05
4300
发布2023-12-06 17:23:05
举报
文章被收录于专栏:旅途散记

本文是对官方 Profile-guided optimization in Go 1.21[1] 的学习与实践.

对于PGO的思路,之前就有过类似的想法,有些许差异. 但本质都是通过对以往运行情况的"学习",优化以后程序的运行(有点以史为鉴鉴于往事,资于治道的感觉)

过程很简单:

  1. 收集程序运行过程中的数据。
  2. 编译器根据收集到的数据来分析程序行为,进而做出针对性的性能优化

Profile-guided optimization (PGO). 通过分析Profile来提高程序运行时性能,也称为 profile-directed feedback(PDF)feedback-directed optimization(FDO), 是一项通用的优化技术,在其他语言/软件产品如Chrome中也有使用

亲测体验

项目初始化

以下代码来自官方示例[2]

代码语言:javascript
复制
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语言编写的简单的Web服务器,提供了一个/render的HTTP接口,用于将输入的Markdown文本转换为HTML并返回给客户端。

代码中的import语句导入了一些需要使用的包,包括bytesiolognet/http等。其中net/http/pprof包是用于性能分析。gitlab.com/golang-commonmark/markdown是一个第三方Markdown解析库。

render函数是一个HTTP请求处理函数,它接收POST请求并从请求的主体中读取Markdown文本。然后使用markdown包将Markdown文本转换为HTML,并将结果写入响应的主体中,最后通过HTTP响应返回给客户端。

main函数是程序的入口点。它注册了render函数来处理/render路径的请求,并启动一个HTTP服务器监听端口8080。一旦服务器启动,它将打印一条日志消息,并通过http.ListenAndServe函数来接收和处理传入的HTTP请求。

整体上,这段代码实现了一个简单的Markdown转换服务,通过HTTP接口接收Markdown文本并返回转换后的HTML结果。你可以将这段代码编译并运行,然后通过发送POST请求到http://localhost:8080/render来测试它。

go build -o markdown-nopgo 编译如上代码

./markdown-nopgo 执行

另起一个终端窗口,找一个markdown格式的文档,此处以 Go 项目中的 README.md为例, 获取该README.md: curl -o README.md -L "https://raw.githubusercontent.com/golang/go/c16c2c49e2fa98ae551fc6335215fadd62d33542/README.md"

请求接口: curl --data-binary @README.md http://localhost:8080/render

模拟线上请求,获取profile文件

Go官方这篇博客的作者,写了一个简单的程序[3],来模拟线上的真实负载情况

可以通过执行 go run github.com/prattmic/markdown-pgo/load@latest mock线上的真实请求

同时因为已经导入了 _ "net/http/pprof", 故而可以通过 curl -o cpu.pprof "http://localhost:8080/debug/pprof/profile?seconds=30" 获得profile

得到profile文件后,可以停止两个程序

使用profile文件

当 Go 工具链在主包目录中找到名为 default.pgo 的配置文件时,它将自动启用 PGO。或者, go build 的 -pgo 标志采用用于 PGO 的配置文件的路径

代码语言:javascript
复制
mv cpu.pprof default.pgo
go build -o markdown.withpgo

这样就有了两个二进制文件, markdown-nopgomarkdown.withpgo

可以通过 go version -m markdown.withpgo 检查构建过程中是否启用了 PGO

性能对比

运行未经过pgo优化的二进制程序 ./markdown-nopgo, 然后执行go test github.com/prattmic/markdown-pgo/load -bench=. -count=40 -source $(pwd)/README.md > nopgo.txt, 保存其benchmark结果

运行经过pgo优化的二进制程序./markdown.withpgo,同样执行go test github.com/prattmic/markdown-pgo/load -bench=. -count=40 -source $(pwd)/README.md > withpgo.txt

最后通过 benchstat nopgo.txt withpgo.txt对比结果

(如果没有安装benchstat,可通过go install golang.org/x/perf/cmd/benchstat@latest安装)

尴尬...经过pgo优化,反而性能下降了~

-count=40 改为 -count=100,再次分别执行两个二进制&进行benchmark,之后对比结果

在n=100情况下,有4%的提升..

相关原理(Under the hood)

详细过程参考官方原文的differential profiling(差异分析) --- 即 在程序运行时获取了优化前和优化后的cpu及heap(主要看总分配计数,即 go tool pprof -sample_index=alloc_objects)相关的pprof文件,然后通过 go tool pprof -diff_base cpu.nopgo.pprof cpu.withpgo.pprof 进行对比

内联优化

能够发现 垃圾回收和内存分配的成本得到了降低,原因是 总体分配的数量相比不启用PGO构建优化前更少

其中 mdurl.Parse(该项目中的一个func)的内存分配次数从之前的近500万次减少为0

这是因为 在非 PGO 构建中, mdurl.Parse 被认为太大,不适合内联。然而,因为我们的 PGO profile文件表明对此函数的调用很热,所以编译器确实内联了它们。

比较cpu.nopgo.pprof和cpu.withpgo.pprof能看到mdurl.Parse被内联优化了

常量传播 是什么?有何作用?

常量传播(Constant Propagation)是编译器优化技术中的一种方法,它涉及在编译时替换那些值已知且不变的变量引用,用它们的实际值代替。这个过程可以减少程序运行时的计算量,提高程序执行的效率。

作用

  1. 提高性能:通过在编译时替换常量,减少运行时的计算,从而提升程序运行速度。
  2. 减少代码体积:有时候,常量传播可以帮助消除一些不必要的代码,从而减少最终程序的大小。
  3. 代码优化:作为编译器优化的一部分,它帮助生成更高效、更紧凑的代码。

实际的编译过程中,常量传播可能涉及更复杂的分析和替换,特别是在大型程序和复杂的代码结构中。这种优化有助于提高程序的执行效率,尤其是在涉及大量计算和逻辑判断的情况下。

去虚拟化

在编程语言优化中,“去虚拟化”(Devirtualization)是一种优化技术,通常用于面向对象编程语言中。它的目的是提高程序的运行效率。为了理解去虚拟化,首先需要了解面向对象编程中的“虚拟函数”(或“虚拟方法”)的概念。 在面向对象的编程语言中,例如C++、Java或C#,虚拟函数是一种可以在派生类中被重写的成员函数。当通过基类的指针或引用调用这样的函数时,会发生动态绑定(或晚期绑定),即运行时根据对象的实际类型来决定调用哪个函数版本。这种机制支持多态,但也带来了性能成本,因为每次调用都需要通过虚拟表(v-table)来确定要执行的正确函数。 去虚拟化是一种编译器优化技术,旨在减少或消除这种运行时开销。如果编译器能够在编译时确定一个特定的虚拟函数调用实际上会调用哪个函数版本,那么它可以直接生成对该特定函数版本的调用,而无需通过虚拟表。这样可以减少间接调用,提高程序运行的效率。 去虚拟化的成功取决于编译器能够多大程度上分析和确定对象的实际类型。在某些情况下,例如当对象的类型在编译时是已知的,去虚拟化可以非常有效。然而,在其他情况下,特别是在涉及复杂继承和多态性的情况下,去虚拟化可能不那么容易实现。

更多参考:

PGO: 为你的Go程序提效5%[4]

Profile Guided Optimizations in Go[5]

Go1.20 那些事:PGO、编译速度、错误处理等新特性,你知道多少?[6]

Profile-guided optimization[7]

PGO 是啥,咋就让 Go 更快更猛了?[8]

探究 Go Profile-Guided Optimizations(PGO)[9]

一文读懂Go 1.20引入的PGO性能优化[10]

参考资料

[1]

Profile-guided optimization in Go 1.21: https://go.dev/blog/pgo

[2]

官方示例: https://go.dev/blog/pgo

[3]

简单的程序: https://github.com/prattmic/markdown-pgo

[4]

PGO: 为你的Go程序提效5%: https://colobu.com/2023/09/13/pgo/

[5]

Profile Guided Optimizations in Go: https://landontclipp.github.io/blog/2023/08/25/profile-guided-optimizations-in-go/

[6]

Go1.20 那些事:PGO、编译速度、错误处理等新特性,你知道多少?: https://blog.csdn.net/EDDYCJY/article/details/128910616

[7]

Profile-guided optimization: https://go.dev/doc/pgo

[8]

PGO 是啥,咋就让 Go 更快更猛了?: https://juejin.cn/post/7168692708725227556

[9]

探究 Go Profile-Guided Optimizations(PGO): https://blog.csdn.net/RA681t58CJxsgCkJ31/article/details/127178724

[10]

一文读懂Go 1.20引入的PGO性能优化: https://zhuanlan.zhihu.com/p/609529412

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-12-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 旅途散记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 亲测体验
    • 项目初始化
      • 模拟线上请求,获取profile文件
        • 使用profile文件
          • 性能对比
          • 相关原理(Under the hood)
            • 内联优化
              • 常量传播 是什么?有何作用?
            • 去虚拟化
              • 参考资料
              相关产品与服务
              云服务器
              云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档