Sebelumnya pada tahun 2023, Go 1.20 Setelah mengatasi keterbatasan yang diketahui dalam preview, dan dengan perbaikan tambahan berkat umpan balik dan kontribusi komunitas, dukungan PGO di Go 1.21 siap untuk penggunaan produksi umum! Untuk dokumentasi lengkap. Cara Mengoptimalkan Profile-Guided Optimization (PGO) Panduan Pengguna Optimasi Profile kita akan berjalan melalui contoh penggunaan PGO untuk meningkatkan kinerja aplikasi. sebelum kita sampai pada itu, apa sebenarnya adalah “optimalisasi yang dipandu oleh profil”? di bawah Ketika Anda membangun biner Go, kompilator Go melakukan optimisasi untuk mencoba menghasilkan biner yang terbaik. Misalnya, proliferasi konstan dapat mengevaluasi ekspresi konstan pada waktu kompilasi, menghindari biaya evaluasi runtime. Analisis Escape menghindari alokasi tumpukan untuk objek lokal-scoped, menghindari GC overheads. Inlining menyalin tubuh fungsi sederhana menjadi panggilan, sering memungkinkan optimasi lebih lanjut di panggilan (seperti proliferasi konstan tambahan atau analisis escape yang lebih baik). Devirtualization mengkonversi panggilan tidak langsung pada nilai antarmuka yang jenisnya dapat ditentukan secara statis menjadi panggilan langsung ke metode beton (yang sering memungkinkan inlining panggilan). Go meningkatkan optimisasi dari rilis ke rilis, tetapi melakukannya bukanlah tugas yang mudah. Beberapa optimisasi dapat disesuaikan, tetapi kompilator tidak bisa hanya "mengubahnya hingga 11" pada setiap optimisasi karena optimisasi yang terlalu agresif sebenarnya dapat merusak kinerja atau menyebabkan waktu build yang berlebihan. optimisasi lain membutuhkan kompilator untuk membuat penilaian tentang apa yang menjadi jalan "biasa" dan "tidak biasa" dalam fungsi. Atau bisa ya? Tanpa informasi definitif tentang bagaimana kode digunakan dalam lingkungan produksi, kompilator hanya dapat beroperasi pada kode sumber paket. Jika kita memberikan profil kepada kompilator, itu dapat membuat keputusan yang lebih terinformasi: lebih agresif mengoptimalkan fungsi yang paling sering digunakan, atau lebih tepat memilih kasus umum. profiling Menggunakan profil perilaku aplikasi untuk optimasi kompiler dikenal sebagai Juga dikenal sebagai Feedback-Directed Optimization (FDO). Optimasi Berbasis Profil (PGO) Contohnya Mari kita membangun layanan yang mengkonversi Markdown ke HTML: pengguna mengunggah sumber Markdown ke , yang mengembalikan konversi HTML. kita dapat menggunakan untuk menerapkannya dengan mudah. /render gitlab.com/golang-commonmark/markdown Berdiri $ go mod init example.com/markdown $ go get gitlab.com/golang-commonmark/markdown@bf3e522c626a Dalam : main.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)) } Mengatur dan mengoperasikan server: $ go build -o markdown.nopgo.exe $ ./markdown.nopgo.exe 2023/08/23 03:55:51 Serving on port 8080... Mari kita coba mengirimkan beberapa Markdown dari terminal lain. dari proyek Go sebagai dokumen sampel: 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 <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> ... Profile yang Sekarang kita memiliki layanan yang bekerja, mari kita mengumpulkan profil dan membangun kembali dengan PGO untuk melihat apakah kita mendapatkan kinerja yang lebih baik. Dalam Kami mengimpor yang secara otomatis menambahkan a Anda dapat mengakses server untuk mengumpulkan profil CPU. main.go WEB atau HTTP/pprof /debug/pprof/profile Biasanya Anda ingin mengumpulkan profil dari lingkungan produksi Anda sehingga kompilator mendapatkan tampilan representatif dari perilaku dalam produksi. untuk menghasilkan muatan saat kami mengumpulkan profil. mengumpulkan dan memulai generator muatan (pastikan server masih berjalan!): Program yang sederhana $ go run github.com/prattmic/markdown-pgo/load@latest Sementara itu berjalan, unduh profil dari server: $ curl -o cpu.pprof "http://localhost:8080/debug/pprof/profile?seconds=30" Setelah ini selesai, bunuh generator muatan dan server. Menggunakan Profil Toolchain Go akan secara otomatis mengaktifkan PGO ketika menemukan profil yang bernama dalam direktori paket utama. sebagai alternatif, Bendera yang mengambil jalur ke profil untuk digunakan untuk PGO. default.pgo -pgo go build Kami menyarankan untuk berkomitmen Menyimpan profil di samping kode sumber Anda memastikan bahwa pengguna secara otomatis memiliki akses ke profil hanya dengan mengambil repository (baik melalui sistem kontrol versi, atau melalui Dan bangunan itu tetap dapat diulang. default.pgo go get Mari kita membangun : $ mv cpu.pprof default.pgo $ go build -o markdown.withpgo.exe Kami dapat memeriksa apakah PGO diaktifkan dalam konstruksi dengan : go version $ go version -m markdown.withpgo.exe ./markdown.withpgo.exe: go1.21.0 ... build -pgo=/tmp/pgo121/default.pgo Evaluasi Kami akan menggunakan benchmark Go untuk mengevaluasi dampak PGO pada kinerja. Cara Menggunakan Load Generator Pertama, kita akan membandingkan server tanpa PGO. $ ./markdown.nopgo.exe Sementara itu berjalan, menjalankan beberapa iterasi benchmark: $ go get github.com/prattmic/markdown-pgo@latest $ go test github.com/prattmic/markdown-pgo/load -bench=. -count=40 -source $(pwd)/README.md > nopgo.txt Setelah itu selesai, bunuh server asli dan mulai versi dengan PGO: $ ./markdown.withpgo.exe Sementara itu berjalan, menjalankan beberapa iterasi benchmark: $ go test github.com/prattmic/markdown-pgo/load -bench=. -count=40 -source $(pwd)/README.md > withpgo.txt Setelah selesai, mari kita bandingkan hasilnya: $ go install golang.org/x/perf/cmd/benchstat@latest $ benchstat nopgo.txt withpgo.txt goos: linux goarch: amd64 pkg: github.com/prattmic/markdown-pgo/load cpu: Intel(R) Xeon(R) W-2135 CPU @ 3.70GHz │ nopgo.txt │ withpgo.txt │ │ sec/op │ sec/op vs base │ Load-12 374.5µ ± 1% 360.2µ ± 0% -3.83% (p=0.000 n=40) Versi baru adalah sekitar 3,8% lebih cepat! Dalam Go 1.21, beban kerja biasanya mendapatkan antara 2% dan 7% peningkatan penggunaan CPU dari mengaktifkan PGO. Profil berisi banyak informasi tentang perilaku aplikasi dan Go 1.21 baru saja mulai memecah permukaan dengan menggunakan informasi ini untuk serangkaian optimasi terbatas. rilis masa depan akan terus meningkatkan kinerja karena lebih banyak bagian dari kompilator mengambil keuntungan dari PGO. Langkah selanjutnya Dalam contoh ini, setelah mengumpulkan profil, kami membangun ulang server kami menggunakan kode sumber yang sama persis yang digunakan dalam build asli. Dalam skenario dunia nyata, selalu ada pengembangan yang sedang berlangsung. Jadi kami dapat mengumpulkan profil dari produksi, yang menjalankan kode minggu lalu, dan menggunakannya untuk membangun dengan kode sumber hari ini. Itu benar-benar baik! PGO di Go dapat menangani perubahan kecil ke kode sumber tanpa masalah. Tentu saja, seiring waktu kode sumber akan mengalir lebih banyak, jadi masih penting untuk memperbarui profil sesekali. Untuk informasi lebih lanjut tentang penggunaan PGO, praktik terbaik dan peringatan untuk diketahui, silakan lihat Jika Anda penasaran tentang apa yang terjadi di bawah topi, terus membaca! Panduan Pengguna Optimasi Profile Di bawah hood Untuk mendapatkan pemahaman yang lebih baik tentang apa yang membuat aplikasi ini lebih cepat, mari kita lihat di bawah topi untuk melihat bagaimana kinerja telah berubah. Inlining Untuk mengamati perbaikan inlining, mari kita analisis aplikasi markdown ini dengan dan tanpa PGO. Saya akan membandingkan ini menggunakan teknik yang disebut profil diferensial, di mana kami mengumpulkan dua profil (satu dengan PGO dan satu tanpa) dan membandingkannya. , tidak jumlah waktu yang sama, jadi saya telah menyesuaikan server untuk secara otomatis mengumpulkan profil, dan generator muat untuk mengirimkan jumlah permintaan tetap dan kemudian keluar dari server. work Perubahan yang telah saya lakukan pada server serta profil yang dikumpulkan dapat ditemukan di Generator beban berjalan dengan . https://github.com/prattmic/markdown-pgo -count=300000 -quit Sebagai pemeriksaan konsistensi yang cepat, mari kita lihat total waktu CPU yang dibutuhkan untuk menangani semua permintaan 300k: $ go tool pprof -top cpu.nopgo.pprof | grep "Total samples" Duration: 116.92s, Total samples = 118.73s (101.55%) $ go tool pprof -top cpu.withpgo.pprof | grep "Total samples" Duration: 113.91s, Total samples = 115.03s (100.99%) Waktu CPU turun dari ~118s ke ~115s, atau sekitar 3%. ini sesuai dengan hasil referensi kami, yang merupakan tanda yang baik bahwa profil ini representatif. Sekarang kita bisa membuka profil diferensial untuk mencari tabungan: $ go tool pprof -diff_base cpu.nopgo.pprof cpu.withpgo.pprof File: markdown.profile.withpgo.exe Type: cpu Time: Aug 28, 2023 at 10:26pm (EDT) Duration: 230.82s, Total samples = 118.73s (51.44%) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top -cum Showing nodes accounting for -0.10s, 0.084% of 118.73s total Dropped 268 nodes (cum <= 0.59s) Showing top 10 nodes out of 668 flat flat% sum% cum cum% -0.03s 0.025% 0.025% -2.56s 2.16% gitlab.com/golang-commonmark/markdown.ruleLinkify 0.04s 0.034% 0.0084% -2.19s 1.84% net/http.(*conn).serve 0.02s 0.017% 0.025% -1.82s 1.53% gitlab.com/golang-commonmark/markdown.(*Markdown).Render 0.02s 0.017% 0.042% -1.80s 1.52% gitlab.com/golang-commonmark/markdown.(*Markdown).Parse -0.03s 0.025% 0.017% -1.71s 1.44% runtime.mallocgc -0.07s 0.059% 0.042% -1.62s 1.36% net/http.(*ServeMux).ServeHTTP 0.04s 0.034% 0.0084% -1.58s 1.33% net/http.serverHandler.ServeHTTP -0.01s 0.0084% 0.017% -1.57s 1.32% main.render 0.01s 0.0084% 0.0084% -1.56s 1.31% net/http.HandlerFunc.ServeHTTP -0.09s 0.076% 0.084% -1.25s 1.05% runtime.newobject (pprof) top Showing nodes accounting for -1.41s, 1.19% of 118.73s total Dropped 268 nodes (cum <= 0.59s) Showing top 10 nodes out of 668 flat flat% sum% cum cum% -0.46s 0.39% 0.39% -0.91s 0.77% runtime.scanobject -0.40s 0.34% 0.72% -0.40s 0.34% runtime.nextFreeFast (inline) 0.36s 0.3% 0.42% 0.36s 0.3% gitlab.com/golang-commonmark/markdown.performReplacements -0.35s 0.29% 0.72% -0.37s 0.31% runtime.writeHeapBits.flush 0.32s 0.27% 0.45% 0.67s 0.56% gitlab.com/golang-commonmark/markdown.ruleReplacements -0.31s 0.26% 0.71% -0.29s 0.24% runtime.writeHeapBits.write -0.30s 0.25% 0.96% -0.37s 0.31% runtime.deductAssistCredit 0.29s 0.24% 0.72% 0.10s 0.084% gitlab.com/golang-commonmark/markdown.ruleText -0.29s 0.24% 0.96% -0.29s 0.24% runtime.(*mspan).base (inline) -0.27s 0.23% 1.19% -0.42s 0.35% bytes.(*Buffer).WriteRune Ketika mengidentifikasi , nilai yang ditampilkan dalam pprof adalah antara kedua kelompok tersebut, misalnya, menggunakan 0.46s lebih sedikit waktu CPU dengan PGO daripada tanpa. Untuk menghitung nilai rata-rata yang digunakan dalam penelitian ini, kita perlu menghitung nilai rata-rata yang digunakan dalam penelitian ( dan persentase, karena persentase itu tidak berarti. pprof -diff_base Perbedaan runtime.scanobject gitlab.com/golang-commonmark/markdown.performReplacements flat cum menunjukkan perbedaan utama dengan perubahan kumulatif. yaitu, perbedaan CPU dari fungsi dan semua jalan transitif dari fungsi itu. Ini biasanya akan menunjukkan bingkai paling luar dalam grafik panggilan program kami, seperti atau titik masuk goroutine lainnya. di sini kita dapat melihat sebagian besar tabungan berasal dari Cara Mengatasi Permintaan HTTP top -cum main ruleLinkify menunjukkan perbedaan utama terbatas hanya pada perubahan dalam fungsi itu sendiri. Ini biasanya akan menunjukkan kerangka internal dalam grafik panggilan program kami, di mana sebagian besar pekerjaan sebenarnya terjadi. di sini kita dapat melihat bahwa tabungan individu sebagian besar berasal dari Fungsi yang top runtime Mari kita lihat pada stack panggilan untuk melihat dari mana mereka berasal: (pprof) peek scanobject$ Showing nodes accounting for -3.72s, 3.13% of 118.73s total ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- -0.86s 94.51% | runtime.gcDrain -0.09s 9.89% | runtime.gcDrainN 0.04s 4.40% | runtime.markrootSpans -0.46s 0.39% 0.39% -0.91s 0.77% | runtime.scanobject -0.19s 20.88% | runtime.greyobject -0.13s 14.29% | runtime.heapBits.nextFast (inline) -0.08s 8.79% | runtime.heapBits.next -0.08s 8.79% | runtime.spanOfUnchecked (inline) 0.04s 4.40% | runtime.heapBitsForAddr -0.01s 1.10% | runtime.findObject ----------------------------------------------------------+------------- (pprof) peek gcDrain$ Showing nodes accounting for -3.72s, 3.13% of 118.73s total ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- -1s 100% | runtime.gcBgMarkWorker.func2 0.15s 0.13% 0.13% -1s 0.84% | runtime.gcDrain -0.86s 86.00% | runtime.scanobject -0.18s 18.00% | runtime.(*gcWork).balance -0.11s 11.00% | runtime.(*gcWork).tryGet 0.09s 9.00% | runtime.pollWork -0.03s 3.00% | runtime.(*gcWork).tryGetFast (inline) -0.03s 3.00% | runtime.markroot -0.02s 2.00% | runtime.wbBufFlush 0.01s 1.00% | runtime/internal/atomic.(*Bool).Load (inline) -0.01s 1.00% | runtime.gcFlushBgCredit -0.01s 1.00% | runtime/internal/atomic.(*Int64).Add (inline) ----------------------------------------------------------+------------- Dengan demikian Akhirnya datang dari · The Katakanlah kepada kami bahwa adalah bagian dari pengumpulan sampah, sehingga Pembiayaan harus berupa uang tunai. b. dan lainnya dengan fungsi ? runtime.scanobject runtime.gcBgMarkWorker Panduan GC runtime.gcBgMarkWorker runtime.scanobject nextFreeFast runtime (pprof) peek nextFreeFast$ Showing nodes accounting for -3.72s, 3.13% of 118.73s total ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- -0.40s 100% | runtime.mallocgc (inline) -0.40s 0.34% 0.34% -0.40s 0.34% | runtime.nextFreeFast ----------------------------------------------------------+------------- (pprof) peek writeHeapBits Showing nodes accounting for -3.72s, 3.13% of 118.73s total ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- -0.37s 100% | runtime.heapBitsSetType 0 0% | runtime.(*mspan).initHeapBits -0.35s 0.29% 0.29% -0.37s 0.31% | runtime.writeHeapBits.flush -0.02s 5.41% | runtime.arenaIndex (inline) ----------------------------------------------------------+------------- -0.29s 100% | runtime.heapBitsSetType -0.31s 0.26% 0.56% -0.29s 0.24% | runtime.writeHeapBits.write 0.02s 6.90% | runtime.arenaIndex (inline) ----------------------------------------------------------+------------- (pprof) peek heapBitsSetType$ Showing nodes accounting for -3.72s, 3.13% of 118.73s total ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- -0.82s 100% | runtime.mallocgc -0.12s 0.1% 0.1% -0.82s 0.69% | runtime.heapBitsSetType -0.37s 45.12% | runtime.writeHeapBits.flush -0.29s 35.37% | runtime.writeHeapBits.write -0.03s 3.66% | runtime.readUintptr (inline) -0.01s 1.22% | runtime.writeHeapBitsForAddr (inline) ----------------------------------------------------------+------------- (pprof) peek deductAssistCredit$ Showing nodes accounting for -3.72s, 3.13% of 118.73s total ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- -0.37s 100% | runtime.mallocgc -0.30s 0.25% 0.25% -0.37s 0.31% | runtime.deductAssistCredit -0.07s 18.92% | runtime.gcAssistAlloc ----------------------------------------------------------+------------- tampak seperti Dan beberapa yang lain di top 10 akhirnya datang dari , yang GC Guide memberitahu kita adalah alokator memori. nextFreeFast runtime.mallocgc Mengurangi biaya dalam GC dan allocator berarti bahwa kami mengalokasikan lebih sedikit secara keseluruhan. mari kita lihat profil tumpukan untuk wawasan: $ go tool pprof -sample_index=alloc_objects -diff_base heap.nopgo.pprof heap.withpgo.pprof File: markdown.profile.withpgo.exe Type: alloc_objects Time: Aug 28, 2023 at 10:28pm (EDT) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for -12044903, 8.29% of 145309950 total Dropped 60 nodes (cum <= 726549) Showing top 10 nodes out of 58 flat flat% sum% cum cum% -4974135 3.42% 3.42% -4974135 3.42% gitlab.com/golang-commonmark/mdurl.Parse -4249044 2.92% 6.35% -4249044 2.92% gitlab.com/golang-commonmark/mdurl.(*URL).String -901135 0.62% 6.97% -977596 0.67% gitlab.com/golang-commonmark/puny.mapLabels -653998 0.45% 7.42% -482491 0.33% gitlab.com/golang-commonmark/markdown.(*StateInline).PushPending -557073 0.38% 7.80% -557073 0.38% gitlab.com/golang-commonmark/linkify.Links -557073 0.38% 8.18% -557073 0.38% strings.genSplit -436919 0.3% 8.48% -232152 0.16% gitlab.com/golang-commonmark/markdown.(*StateBlock).Lines -408617 0.28% 8.77% -408617 0.28% net/textproto.readMIMEHeader 401432 0.28% 8.49% 499610 0.34% bytes.(*Buffer).grow 291659 0.2% 8.29% 291659 0.2% bytes.(*Buffer).String (inline) yang Opsi ini menunjukkan kita jumlah alokasi, terlepas dari ukuran. Ini berguna karena kami menyelidiki penurunan penggunaan CPU, yang cenderung berkorelasi lebih banyak dengan jumlah alokasi daripada ukuran. . -sample_index=alloc_objects mdurl.Parse Untuk referensi, mari kita lihat jumlah alokasi total untuk fungsi ini tanpa PGO: $ go tool pprof -sample_index=alloc_objects -top heap.nopgo.pprof | grep mdurl.Parse 4974135 3.42% 68.60% 4974135 3.42% gitlab.com/golang-commonmark/mdurl.Parse Jumlah total sebelumnya adalah 4974135, yang berarti bahwa Anda telah menghapus 100% dari alokasi! mdurl.Parse Kembali ke profil diferensial, mari kita kumpulkan sedikit lebih banyak konteks: (pprof) peek mdurl.Parse Showing nodes accounting for -12257184, 8.44% of 145309950 total ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- -2956806 59.44% | gitlab.com/golang-commonmark/markdown.normalizeLink -2017329 40.56% | gitlab.com/golang-commonmark/markdown.normalizeLinkText -4974135 3.42% 3.42% -4974135 3.42% | gitlab.com/golang-commonmark/mdurl.Parse ----------------------------------------------------------+------------- Panggilan untuk Mereka datang dari dan . mdurl.Parse markdown.normalizeLink markdown.normalizeLinkText (pprof) list mdurl.Parse Total: 145309950 ROUTINE ======================== gitlab.com/golang-commonmark/mdurl.Parse in /usr/local/google/home/mpratt/go/pkg/mod/gitlab.com/golang-commonmark/mdurl@v0.0.0-20191124015652-932350d1cb84/parse .go -4974135 -4974135 (flat, cum) 3.42% of Total . . 60:func Parse(rawurl string) (*URL, error) { . . 61: n, err := findScheme(rawurl) . . 62: if err != nil { . . 63: return nil, err . . 64: } . . 65: -4974135 -4974135 66: var url URL . . 67: rest := rawurl . . 68: hostless := false . . 69: if n > 0 { . . 70: url.RawScheme = rest[:n] . . 71: url.Scheme, rest = strings.ToLower(rest[:n]), rest[n+1:] Sumber lengkap untuk fungsi dan panggilan ini dapat ditemukan di: Merdeka.Parse Keterlibatan.normalizeLink markdown.normalizeLinkTeks Jadi apa yang terjadi di sini? dalam konstruksi non-PGO, dianggap terlalu besar untuk memenuhi syarat untuk inlining. Namun, karena profil PGO kami menunjukkan bahwa panggilan ke fungsi ini panas, kompilator melakukan inline mereka. mdurl.Parse $ go tool pprof -top cpu.nopgo.pprof | grep mdurl.Parse 0.36s 0.3% 63.76% 2.75s 2.32% gitlab.com/golang-commonmark/mdurl.Parse $ go tool pprof -top cpu.withpgo.pprof | grep mdurl.Parse 0.55s 0.48% 58.12% 2.03s 1.76% gitlab.com/golang-commonmark/mdurl.Parse (inline) menciptakan a sebagai variabel lokal pada baris 66 ( dan kemudian mengembalikan pointer ke variabel tersebut pada baris 145 ( Biasanya ini membutuhkan variabel untuk ditugaskan pada tumpukan, sebagai referensi untuk itu hidup di luar return fungsi. yang tertanam dalam , kompiler dapat mengamati bahwa variabel tidak melarikan diri , yang memungkinkan kompilator untuk mendistribusikannya ke stack. Mirip dengan . mdurl.Parse URL var url URL return &url, nil mdurl.Parse markdown.normalizeLink normalizeLink markdown.normalizeLinkText markdown.normalizeLink Penurunan terbesar kedua yang ditunjukkan dalam profil, dari adalah kasus serupa untuk menghilangkan pelarian setelah inlining. mdurl.(*URL).String Bagian dari kekuatan PGO dan optimasi kompilator secara umum adalah bahwa efek pada alokasi tidak merupakan bagian dari implementasi PGO kompilator sama sekali. Semua efek untuk analisis escape dan alokasi heap adalah optimisasi standar yang berlaku untuk setiap build. Perilaku escape yang ditingkatkan adalah efek downstream yang hebat dari inlining, tetapi itu bukan satu-satunya efek. Banyak optimisasi dapat memanfaatkan inlining. misalnya, proliferasi konstan mungkin dapat menyederhanakan kode dalam fungsi setelah inlining ketika beberapa input konstan. Devirtualisasi Selain inling, yang kita lihat dalam contoh di atas, PGO juga dapat mendorong devirtualisasi kondisional panggilan antarmuka. Sebelum kita masuk ke PGO-driven devirtualization, mari kita mundur dan mendefinisikan “devirtualization” secara umum. f, _ := os.Open("foo.txt") var r io.Reader = f r.Read(b) Berikut ini adalah panggilan untuk Metode Interface Karena antarmuka dapat memiliki beberapa implementasi, kompilator menghasilkan panggilan fungsi, yang berarti memeriksa metode yang tepat untuk panggilan pada waktu berjalan dari jenis dalam nilai antarmuka. panggilan tidak langsung memiliki biaya runtime tambahan kecil dibandingkan dengan panggilan langsung, tetapi yang lebih penting mereka mengecualikan beberapa optimasi kompiler. misalnya, kompilator tidak dapat melakukan analisis escape pada panggilan tidak langsung karena tidak tahu implementasi metode konkret. io.Reader Read tidak langsung Dalam contoh di atas, kita Metode-metode yang digunakan harus benar-benar Sejak adalah satu-satunya jenis yang dapat ditugaskan untuk Dalam hal ini, kompilator akan melakukan , dimana menggantikan panggilan tidak langsung untuk Dengan panggilan langsung ke sehingga memungkinkan optimasi lainnya. Do yang os.(*File).Read *os.File r Devirtualisasi io.Reader.Read os.(*File).Read (Anda mungkin berpikir "kode itu tidak berguna, mengapa seseorang akan menulisnya seperti itu?" Ini adalah poin yang baik, tetapi perhatikan bahwa kode seperti di atas mungkin hasil dari inlining. menjadi bagian dari suatu fungsi yang mengambil Setelah fungsi tersebut diintegrasikan, maka fungsi menjadi sesuatu yang konkret.) f io.Reader io.Reader PGO-driven devirtualization memperluas konsep ini ke situasi di mana jenis beton tidak diketahui secara statis, tetapi profiling dapat menunjukkan bahwa, misalnya, Memanggil Target Dalam kasus ini, PGO dapat menggantikan Dengan sesuatu seperti: io.Reader.Read os.(*File).Read r.Read(b) if f, ok := r.(*os.File); ok { f.Read(b) } else { r.Read(b) } Artinya, kita menambahkan pemeriksaan runtime untuk jenis beton yang paling mungkin muncul, dan jika demikian, gunakan panggilan beton, atau jika tidak jatuh kembali ke panggilan tidak langsung standar. ) dapat disusun dan memiliki optimisasi tambahan yang diterapkan, tetapi kami masih mempertahankan jalur terbalik karena profil tidak menjamin bahwa ini akan selalu terjadi. *os.File Dalam analisis server markdown kami, kami tidak melihat devirtualisasi yang didorong oleh PGO, tetapi kami juga hanya melihat area yang paling terpengaruh. PGO (dan sebagian besar optimasi kompiler) umumnya menghasilkan keuntungan mereka dalam agregat perbaikan yang sangat kecil di banyak tempat yang berbeda, jadi mungkin ada lebih banyak yang terjadi daripada apa yang kami lihat. Inlining dan devirtualization adalah dua optimisasi yang didorong oleh PGO yang tersedia di Go 1.21, tetapi seperti yang telah kita lihat, ini sering membuka optimisasi tambahan. Pengakuan Menambahkan optimasi berbasis profil ke Go telah menjadi upaya tim, dan saya pasti ingin menyebutkan kontribusi dari Raj Barik dan Jin Lin di Uber, dan Cherry Mui dan Austin Clements di Google. oleh Michael Pratt Artikel ini tersedia di The Go Blog di bawah lisensi CC BY 4.0 DEED. Blog yang Go Blog yang Go Foto oleh Ben Sweet di Unsplash Ben yang manis Unsplash yang