Read the PGO performance optimization introduced by Go 1.20 in one article

background

Go version 1.20 was officially released in February 2023, and the PGO performance optimization mechanism was introduced in this version.

The full English name of PGO is Profile Guided Optimization. The basic principle is divided into the following two steps:

  • First do profiling on the program, collect data when the program is running, and generate profiling files.
  • Enable the PGO option when compiling the program, and the compiler will optimize the performance of the program according to the content in the .pgo file.

We all know that when compiling a program, the compiler will do a lot of optimizations to the program, including the well-known inline optimization (inline optimization), escape analysis (escape analysis), and constant propagation (constant propagation). These are optimizations that compilers can implement directly by analyzing the program source code.

But some optimizations cannot be achieved by parsing the source code.

For example, there are many if/else conditional branch judgments in a function. We may hope that the compiler will automatically optimize the conditional branch order for us to speed up the conditional branch judgment and improve program performance.

However, the compiler may not be able to know which conditional branches are entered more often and which conditional branches are entered less frequently, because this is related to the input of the program.

At this time, people who do compiler optimization think of PGO: Profile Guided Optimization.

The principle of PGO is very simple, that is to run the program first and collect data during the running of the program. Then the compiler analyzes the behavior of the program based on the collected program runtime data, and then performs targeted performance optimization.

For example, the program can collect which conditional branches enter more times, and put the judgment of the conditional branch in front, which can reduce the time-consuming condition judgment and improve program performance.

So how does the Go language use PGO to optimize the performance of the program? Let's look at specific examples next.

example

We implement a web interface /renderthat takes the binary format of the markdown file as input and converts the markdown format into html format and returns it.

We implement this interface with the help of gitlab.com/golang-commonmark/markdownthe project .

Environment build

$ go mod init example.com/markdown

Create a new main.gofile with the following code:

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))
}

Compile and run the program:

$ go mod tidy
$ go build -o markdown.nopgo
$ ./markdown.nopgo
2023/02/25 22:30:51 Serving on port 8080...

Create a new file in the main directory of the program input.md, the content can be customized, and it only needs to conform to the markdown syntax.

The markdown file input.md is used in the example I demonstrated .

Send the binary content of the markdown file to the interface through the curl command /render.

$ curl --data-binary @input.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>
...

You can see that the interface returns input.mdthe html format corresponding to the file content.

Profiling

Then we main.godo profiling for the program, get the data when the program is running, and then perform performance optimization through PGO.

In main.go, there is import net/http/pprof/render library, which will add a new web interface based on the original existing web interface /debug/pprof/profile. We can request the profiling interface to obtain the data when the program is running.

  • Under the main directory of the program, add a load subdirectory, and add main.gofiles under the load subdirectory. load/main.goWhen running, it will continuously request the interface ./markdown.nogpoof the server started above /renderto simulate the actual running situation of the program.

    $ go run example.com/markdown/load
    
  • Request the profiling interface to obtain program runtime data.

    $ curl -o cpu.pprof "http://localhost:8080/debug/pprof/profile?seconds=30"
    

​ Wait for 30 seconds, the curl command will end, and a file will be generated in the main directory of the program cpu.pprof.

Note : Go version 1.20 must be used to compile and run the program.

PGO optimizer

$ mv cpu.pprof default.pgo
$ go build -pgo=auto -o markdown.withpgo

go buildWhen compiling the program, enable -pgothe option.

-pgoBoth specified profiling files and autopatterns can be supported.

If it is autoa pattern, it will automatically find default.pgothe profiling file named in the main directory of the program.

Go officially recommends that you use autothe mode, and default.pgostore the files in the main directory of the program for maintenance, so that all developers of the project can use it default.pgoto optimize the performance of the program.

In version 1.20 of Go, -pgothe default value of the option is off, we must add -pgo=autoto enable PGO optimization.

In future Go versions, the official plan is -pgoto set the default value of the option to auto.

performance comparison

loadAdd a new bench_test.gofile in the subdirectory of the program , bench_test.goand use the Go performance testing Benchmark framework to stress test the server.

Scenarios without PGO optimization enabled

Enable the server program without PGO optimization enabled:

$ ./markdown.nopgo

Start the stress test:

$ go test example.com/markdown/load -bench=. -count=20 -source ../input.md > nopgo.txt

Scenarios for enabling PGO optimization

Enable the server program with PGO optimization enabled:

$ ./markdown.withpgo

Start the stress test:

$ go test example.com/markdown/load -bench=. -count=20 -source ../input.md > withpgo.txt

comprehensive comparison

nopgo.txtUse the sum obtained from the above stress test withpgo.txtfor performance comparison.

$ go install golang.org/x/perf/cmd/benchstat@latest
$ benchstat nopgo.txt withpgo.txt
goos: darwin
goarch: amd64
pkg: example.com/markdown/load
cpu: Intel(R) Core(TM) i5-5250U CPU @ 1.60GHz
       │  nopgo.txt  │             withpgo.txt             │
       │   sec/op    │   sec/op     vs base                │
Load-4   447.3µ ± 7%   401.3µ ± 1%  -10.29% (p=0.000 n=20)

It can be seen that after using PGO optimization, the performance of the program has been improved by 10.29%, which is very impressive.

In Go version 1.20, after using PGO, the performance of the program can usually be improved by about 2%-4%.

In subsequent versions, the compiler will continue to optimize the PGO mechanism to further improve program performance.

Summarize

Go version 1.20 introduced PGO to allow the compiler to optimize the performance of the program. PGO is used in 2 steps:

  • Get a profiling file first.
  • Enable go buildthe PGO option when compiling, and guide the compiler to optimize the performance of the program through the profiling file.

In the production environment, we can collect recent profiling data, and then use PGO to optimize the program to improve system processing performance.

More instructions and best practices about PGO can be found in the profile-guided optimization user guide .

Source code address: pgo optimization source code .

recommended reading

open source address

Articles and sample code are open source on GitHub: Go Language Beginner, Intermediate, and Advanced Tutorials .

Official account: advanced coding. Follow the official account to get the latest Go interview questions and technology stack.

Personal website: Jincheng's Blog .

Zhihu: Wuji .

Welfare

I have compiled a gift pack of back-end development learning materials for you, including programming language entry to advanced knowledge (Go, C++, Python), back-end development technology stack, interview questions, etc.

Follow the official account "coding advanced", send a message to backend to receive a data package, this data will be updated from time to time, and add data that I think is valuable. You can also send a message " Join the group " to communicate and learn with your peers, and answer questions.

References

  • https://go.dev/blog/pgo-preview

Guess you like

Origin blog.csdn.net/perfumekristy/article/details/129224617