亲测体验Go语言PGO

news2024/9/24 1:26:50

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


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

alt

过程很简单:

  1. 收集程序运行过程中的数据。

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


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


亲测体验


项目初始化


alt

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

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

alt

模拟线上请求,获取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

alt

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


使用profile文件


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

alt
mv cpu.pprof default.pgo
go build -o markdown.withpgo

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


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

alt

性能对比


运行未经过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安装)

alt

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

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

alt

在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被内联优化了

alt

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

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

作用

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

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


去虚拟化


alt

在编程语言优化中,“去虚拟化”(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

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1285418.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

zabbix的自动发现机制:

zabbix的自动发现机制: zabbix客户端主动的和服务端联系,将自己的地址和端口发送给服务端,实现自动添加监控主机 客户端是主动的一方 缺点;如果自定义网段中主机数量太多,等级耗时会很久,而且这个自动发现机制不是很…

银河麒麟服务器部署Prometheus+Grafana

#年薪百万# 一、环境准备 操作系统:Operating System: Kylin Linux Advanced Server V10 (Sword) (X86-64) prometheus:prometheus-2.48.0.linux-amd64 grafana:grafana-enterprise-10.2.2.linux-amd64 node_exporter:node_expor…

个体诊所电子处方系统哪个好用,推荐一款可以自由设置配方模板能够填写病历可以查询历史病历的门诊处方笺软件

一、前言 1、功能实用,操作简单,不会电脑也会操作,软件免安装,已内置数据库。 2、中医西医均可用此软件开电子处方,支持病历记录查询,药品进出库管理,支持配方模板一键导入电子处方。 二、电子…

Anaconda创建虚拟环境以及Pycharm和Jupyter如何切换虚拟环境

文章目录 Anaconda创建管理虚拟环境0. 进入到终端1. 创建新环境2. 切换环境3. 删除环境4. 查询当前已有的环境 Pycharm切换虚拟环境0. 更换解析器1. 添加虚拟环境(之前默认的是base环境)2. 验证切换虚拟环境 Jupyter Notebook 切换虚拟环境1. 安装ipyker…

FPGA串口接收解帧、并逐帧发送有效数据——1

FPGA串口接收解帧、并逐帧发送有效数据 工程实现的功能:FPGA串口接收到串口调试助手发来的数据,将其数据解帧。判断到正确的帧头和帧尾之后,将有效数据存入rx_data中;另一方面发送端将有效数据逐帧发送出去。 参考:正…

嵌入式门槛高吗?

今日话题,嵌入式门槛高吗?在嵌入式领域,门槛因公司和职位的不同而异。普通的嵌入式岗位门槛相对较低,通常要求掌握一些C语言编程和单片机相关知识,可以制作简单的电子产品,但相应的工资较低。然而&#xff…

HNU-电路与电子学-未知年份(不含解析)

【写在前面】 电路与电子学好像是从2020级开设的课程,故实际上目前只有2020与2021两个年级考过期末考试。 这门课程主要由所谓的“数电”与“模电”组成。而且先学的“模电”后学的“”数电,故期中考试主要以“模电”为主,期末考试主要以“…

编译WSL内核,用于操作usb读卡器

wsl2默认不能操作usb读卡器,但是对于嵌入式linux开发来说,需要经常对tf卡进行操作,随时都会使用到usb读卡器的访问。下面讲述如何开启wsl2的usb读卡器的访问,主要涉及到以下2个步骤: wsl2本质是一个虚拟机&#xff0c…

【GO】protobuf在golang中的测试用例

上篇文章介绍了如何安装protobuf环境,文章链接如下 【Go】protobuf介绍及安装-CSDN博客 本节介绍protobuf在gRPC中具体如何使用,并编写测试用例 一、Protobuf是如何工作的 .proto文件是protobuf一个重要的文件,它定义了需要序列化数据的结…

Ubuntu 20.0 + mysql 8.0 用户和密码修改

第一步 下载(简单,注意联网)Ubuntu 终端输入以下两行命令 (1) 数据库的服务端及客户端数据库的开发软件包 sudo apt-get install mysql-server mysql-client (2) 数据库的开发软件包 sudo apt-get install libmysqlclient-dev 第二步 查看是否安装成功 …

深度分析电动工具的发展趋势,盘点几个极具潜力的科技功能点

纵观市场发展规律,人类的每一次能源转型,都会带来大量红利商机,也会催生整个产业链的彻底革新。 一、电动工具的惊人爆发力 比如说电动工具这个大品类,在近两年意想不到地成为全球隐形增长冠军。主要原因在于海外市场有大量 DIY…

HNU-电路与电子学-2019期末A卷(不含解析)

【写在前面】 电路与电子学好像是从2020级开设的课程,故实际上目前只有2020与2021两个年级考过期末考试。 这门课程主要由所谓的“数电”与“模电”组成。而且先学的“模电”后学的“”数电,故期中考试主要以“模电”为主,期末考试主要以“数…

持续集成交付CICD:GitLab Webhook触发Jenkins流水线

目录 一、实验 1.Jenkins远程下载GiaLab仓库代码 2.curl远程触发Jenkins流水线 3.GitLab Webhook触发Jenkins流水线 二、问题 1.GitLab配置Webhook时报错 一、实验 1.Jenkins远程下载GiaLab仓库代码 (1) Jenkins添加选项参数 (2)添加字符参数 (3)查看构建参数情况 (4)添…

孩子都能学会的FPGA:第二十一课——用线性反馈移位寄存器实现伪随机序列

(原创声明:该文是作者的原创,面向对象是FPGA入门者,后续会有进阶的高级教程。宗旨是让每个想做FPGA的人轻松入门,作者不光让大家知其然,还要让大家知其所以然!每个工程作者都搭建了全自动化的仿…

神经网络常用归一化和正则化方法解析(一)

🎀个人主页: https://zhangxiaoshu.blog.csdn.net 📢欢迎大家:关注🔍点赞👍评论📝收藏⭐️,如有错误敬请指正! 💕未来很长,值得我们全力奔赴更美好的生活&…

CRM:提升营销效果的关键

一场成功的营销活动,可以帮助企业扩大知名度,获取大量的优质商机。作为专业的管理软件,CRM系统同样具备营销管理的能力,帮助企业实现营销活动的规划、执行和监控,提高营销效果。下面说说,CRM营销自动化对企…

职场人的年底总结,年初规划,又要开始啦!

在2023年,你可能错过了某些重要的职业发展机会,或者错失了一些与家人和朋友共度的时光。也可能经历了企业的降本增效,面临了被否定和裁员的风险;或者是得到了企业的重用和提拔,一个人撑起了整个业务部门。这些经历不仅…

Python中函数添加超时时间,Python中signal使用

from time import time, sleepimport signal# 模拟要删除5条数据,中间有超时的i 5# 超时后执行的方法def timeout_handler(signal, frame):# 引发异常raise TimeoutError("删除第" str(i) "条,超时!")# 或者执行其他操作,不往外抛异常(超时的函数不会被…

【排障记录】Oracle自动归档清理任务无法进行(windows平台),原因居然是?

前言 接用户求助,生产业务上的一套数据库存在归档文件占用过多磁盘空间的问题(约四万个),需要清理,最好设置成定期自动清理,以减少人工干预。 处置过程 由于Oracle搭建在windows操作系统之上&#xff0c…

spring boot mybatis TypeHandler 源码如何初始化及调用

目录 概述使用TypeHandler使用方式在 select | update | insert 中加入 配置文件中指定 源码分析配置文件指定Mapper 执行query如何转换 结束 概述 阅读此文 可以达到 spring boot mybatis TypeHandler 源码如何初始化及如何调用的。 spring boot 版本为 2.7.17,my…