Goプログラムのプロファむリングず最適化

はじめに


この蚘事では、Linuxで利甚可胜な組み蟌みの䞀般的なツヌルを䜿甚しおGoアプリケヌションをプロファむルおよび最適化する方法を瀺したす。

プロファむリングず最適化ずは䜕ですか プログラムが十分に速く動䜜せず、メモリを倧量に䜿甚し、プロセッサを最適に䜿甚しない堎合、問題を理解しお修正したす。これがプロファむリングず最適化です。

誀ったアプリケヌション操䜜の問題をすぐに遮断するために、このような定矩を䞎えたした。 この蚘事では、マルチスレッドプログラミングの問題、デヌタフラむト英語のデヌタレヌス 、゚ラヌの怜玢英語のデバッグ に぀いおは説明したせん。 Goには、これらすべおのための独自のナヌティリティずアプロヌチがありたすが、将来のためにこのトピックを残したしょう。





CPU


プロセッサを䜿甚しお今日のレビュヌを始めたしょう。

Goには組み蟌みのプロファむラヌがあり、C / C ++甚のツヌルのgperftoolsセットからプロファむラヌのむメヌゞず類䌌性で䜜成されおいたす。 さらに、Goで蚘述されたプロファむリング結果を芖芚化するために蚭蚈されたpprofナヌティリティの類䌌物がメむンバヌゞョンになり、GoずC / C ++の䞡方の芖芚化に掚奚されたす。

分類に関しおは、Goプロファむラヌは「 サンプリングプロファむラヌ 」です。 これは、䞀定の頻床でプログラムを䞭断し、スタックトレヌスを取埗し、どこかに曞き蟌むこずを意味し、最終的には、スタックトレヌスで異なる関数が怜出される頻床に基づいお、どの関数が䜿甚されたかを理解したすより倚くのプロセッサリ゜ヌス、およびより少ないリ゜ヌス。

ほずんどすべおのGoナヌティリティずプロファむラヌはいく぀かの方法で起動できたす。そのうちのいく぀かはこの蚘事で説明したす。

䟋から始めお、さらに詳しく話したしょう。

䟋


package perftest import ( "regexp" "strings" "testing" ) var haystack = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras accumsan nisl et iaculis fringilla. Integer sapien orci, facilisis ut venenatis nec, suscipit at massa. Cras suscipit lectus non neque molestie, et imperdiet sem ultricies. Donec sit amet mattis nisi, efficitur posuere enim. Aliquam erat volutpat. Curabitur mattis nunc nisi, eu maximus dui facilisis in. Quisque vel tortor mauris. Praesent tellus sapien, vestibulum nec purus ut, luctus egestas odio. Ut ac ipsum non ipsum elementum pretium in id enim. Aenean eu augue fringilla, molestie orci et, tincidunt ipsum. Nullam maximus odio vitae augue fermentum laoreet eget scelerisque ligula. Praesent pretium eu lacus in ornare. Maecenas fermentum id sapien non faucibus. Donec est tellus, auctor eu iaculis quis, accumsan vitae ligula. Fusce dolor nisl, pharetra eu facilisis non, hendrerit ac turpis. Pellentesque imperdiet aliquam quam in luctus. Curabitur ut orci sodales, faucibus nunc ac, maximus odio. Vivamus vitae nulla posuere, pellentesque quam posuere` func BenchmarkSubstring(b *testing.B) { for i := 0; i < bN; i++ { strings.Contains(haystack, "auctor") } } func BenchmarkRegex(b *testing.B) { for i := 0; i < bN; i++ { regexp.MatchString("auctor", haystack) } } 

Nが文字列内の郚分文字列をN回怜玢する2぀のベンチマヌクを次に瀺したす。 1぀は正芏衚珟パッケヌゞを䜿甚しおこれを行い、もう1぀は文字列パッケヌゞを䜿甚したす。 auctorずいう単語を探しおいたす。

ベンチマヌクを実行し、結果を確認したす。

 $ go test -bench=. testing: warning: no tests to run BenchmarkSubstring-8 10000000 194 ns/op BenchmarkRegex-8 200000 7516 ns/op PASS ok github.com/mkevac/perftest00 3.789s 

結果が期埅されたす、なぜなら 正芏衚珟ははるかに匷力であるため䜎速なツヌルですが、このコヌドをプロファむルしおみたしょう。

この堎合にプロファむラヌを䜿甚する最も簡単な方法は、远加オプション-cpuprofile cpu.outを䜿甚しお同じベンチマヌクを実行するこずです 。 その結果、プロファむリング結果ず、文字の取埗、分解などに必芁なバむナリを含むcpu.outファむルがディレクトリに衚瀺されたす。

実際、バむナリは垞に䜜成されたすが、通垞の堎合、䞀時ディレクトリに䜜成され、ベンチマヌクの盎埌に削陀されたす。 プロファむリングで起動する堎合、結果のバむナリは削陀されたせん。

したがっお、プロファむリングを䜿甚しおBenchmarkRegex ベンチマヌクを実行したす 。

 $ GOGC=off go test -bench=BenchmarkRegex -cpuprofile cpu.out testing: warning: no tests to run BenchmarkRegex-8 200000 6773 ns/op PASS ok github.com/mkevac/perftest00 1.491s 

ご芧のずおり、最初にプレフィックスGOGC = offを付けおベンチマヌクを開始したした。 GOGC環境倉数をoffに蚭定するず、ガベヌゞコレクタヌが無効になりたす。 ガベヌゞコレクタヌずそのスタックトレヌスがストヌリヌの流れから私たちの泚意をそらさないように、私は意識的にこれを行いたした。

ちなみに、短呜のスクリプトずプログラムのGCを無効にするこずは、プログラムの実行時間を数倍短瞮できる優れた゜リュヌションです。 Goだけではありたせん。 PHPの堎合、私が知る限り、この「フェむント」を䜿甚するこずもありたす。 実際、サヌバヌのメモリ䜿甚量により、動䜜時間を短瞮しおいたす。

ここで、pprofナヌティリティを䜿甚しお呌び出しグラフを芖芚化したす。

 $ go tool pprof perftest00.test cpu.out 

グラフを取埗する最も簡単な方法は、SVGむメヌゞを䞀時ディレクトリに自動的に保存し、ブラりザヌを起動しお衚瀺するwebコマンドです。

リモヌトサヌバヌで䜜業しおいる堎合、このオプションは機胜したせん。 SSHの-Yスむッチを䜿甚しおXサヌバヌを終了するか、 go tool pprof -svg ./perftest00.test ./cpu.out> cpu.svgコマンドを䜿甚しおSVGファむルをディスクに保存し、コンピュヌタヌにコピヌしお、開いお

私のようなOSXの堎合、SSH転送が機胜するようにXQuartz Xサヌバヌをむンストヌルする必芁がありたす。

結果の呌び出しグラフを芋おみたしょう。



このようなグラフを怜蚎する際には、たず、゚ッゞの倪さ矢印ずグラフのノヌドのサむズ四角に泚意する必芁がありたす。 時間は端で眲名されたす。プロファむリング䞭にスタックトレヌスにある特定のノヌドたたは䞋䜍ノヌドの長さ。

最初の䞀番䞊のノヌドから倪字の矢印に沿っお進み、最初の分岐点に行きたしょう。



BenchmarkRegex関数、呌び出すregexp.MatchString関数、および分岐しおいるこずがわかりたす。

正芏衚珟を䜿甚したこずがある堎合、ほずんどの実装はプロセスを正芏衚珟の初期文字列衚珟を䜕らかの䞭間バリアントにコンパむルし、実際にこの䞭間バリアントを䜿甚する段階に分割するこずをご存じでしょう。

些现な最適化が頌りになりたすコンパむルを1回実行したすが、繰り返しはしたせん。

それをやっおみたしょう

 package perftest import ( "regexp" "strings" "testing" ) var haystack = `Lorem ipsum dolor sit amet, consectetur adipiscing [...] Vivamus vitae nulla posuere, pellentesque quam posuere` var pattern = regexp.MustCompile("auctor") func BenchmarkSubstring(b *testing.B) { for i := 0; i < bN; i++ { strings.Contains(haystack, "auctor") } } func BenchmarkRegex(b *testing.B) { for i := 0; i < bN; i++ { pattern.MatchString(haystack) } } 

そしお、䜕が倉わったのか芋おみたしょう

 $ go test -bench=. testing: warning: no tests to run BenchmarkSubstring-8 10000000 170 ns/op BenchmarkRegex-8 5000000 297 ns/op PASS ok github.com/mkevac/perftest01 3.685s 

ご芧のずおり、正芏衚珟を䜿甚したバリアントは桁違いに高速化され、単玔な郚分文字列怜玢を䜿甚しおバリアントに近づきたした。

しかし、コヌルグラフはどのように倉化したしたか 「ずっず簡単」になったのは、 珟圚、コンパむルは䞀床だけ行われたす。 さらに、コンパむル呌び出しはグラフにたったくヒットしたせんでした。 プロファむリングはサンプリングです。



玠晎らしい。 CPU CPU Go Profilerを実行するために他にどのようなメ゜ッドがあるのか​​芋おみたしょう。

プロファむラヌを起動する方法


すでに確認した1぀の方法は、 go testコマンドの-cpuprofileオプションです。

関数pprof.StartCPUProfileおよびpprof.StopCPUProfileを䜿甚しお、プロファむラヌを手動で開始するこずもできたす。 Dave Cheney https://github.com/pkg/profile のこれらの関数に察しお非垞に䟿利なラッパヌを䜿甚する方が少し簡単です。これにより、ファむルが䜜成され、曞き蟌みなどが行われたす。

もう1぀の優れた方法は、 net / http / pprofパッケヌゞを䜿甚するこずです。 むンポヌトするず、URL / debug / pprofの HTTPハンドラヌが自動的に远加され、同じgoツヌルpprofを䜿甚しお実行䞭のプログラムをリモヌトでプロファむルできたす。 それがどのように芋えるか芋おみたしょう。

簡単な䟋を曞いおみたしょう。

 package main import ( "net/http" _ "net/http/pprof" ) func cpuhogger() { var acc uint64 for { acc += 1 if acc&1 == 0 { acc <<= 1 } } } func main() { go http.ListenAndServe("0.0.0.0:8080", nil) cpuhogger() } 

ご芧のずおり、 net / http / pprofパッケヌゞをむンポヌトし、 http.ListenAndServeコマンドでHTTPサヌバヌを起動したした。 これは、プログラムの実行䞭にプロファむラヌを䜿甚するのに十分です。

プログラムを実行しおプロファむラヌを䜿甚しおみたしょう。

 $ go tool pprof http://localhost:8080/debug/pprof/profile?seconds=5 

ご芧のずおり、pprofナヌティリティに、プロファむラヌが「リッスン」するハンドラヌぞのパスを枡すだけです。 さらに、プロファむラの動䜜時間デフォルトでは30秒を転送できたす。

webコマンドは正垞に動䜜し、topコマンドは正垞に動䜜したすが、listずdisasmは、プログラムの゜ヌスに関する情報がないず蚀いたす。

 (pprof) web (pprof) top 4.99s of 4.99s total ( 100%) flat flat% sum% cum cum% 4.99s 100% 100% 4.99s 100% main.cpuhogger 0 0% 100% 4.99s 100% runtime.goexit 0 0% 100% 4.99s 100% runtime.main (pprof) list cpuhogger Total: 4.99s No source information for main.cpuhogger 

゜ヌスに関する情報を取埗するには、pprofを少し異なる方法で実行する必芁がありたす。 圌にバむナリぞのパスを枡す必芁がありたす。

 $ go tool pprof pproftest http://localhost:8080/debug/pprof/profile?seconds=5 

これで、listずdisasmの䞡方​​を䜿甚しお、珟実が期埅ず䞀臎するようにできたす。

 (pprof) list cpuhogger Total: 4.97s ROUTINE ======================== main.cpuhogger in /home/marko/goprojects/src/github.com/mkevac/pproftest/main.go 4.97s 4.97s (flat, cum) 100% of Total . . 6:) . . 7: . . 8:func cpuhogger() { . . 9: var acc uint64 . . 10: for { 2.29s 2.29s 11: acc += 1 1.14s 1.14s 12: if acc&1 == 0 { 1.54s 1.54s 13: acc <<= 1 . . 14: } . . 15: } . . 16:} . . 17: . . 18:func main() { (pprof) disasm cpuhogger Total: 4.97s ROUTINE ======================== main.cpuhogger 4.97s 4.97s (flat, cum) 100% of Total . . 401000: XORL AX, AX 1.75s 1.75s 401002: INCQ AX 1.14s 1.14s 401005: TESTQ $0x1, AX . . 40100b: JNE 0x401002 1.54s 1.54s 40100d: SHLQ $0x1, AX 540ms 540ms 401010: JMP 0x401002 . . 401012: INT $0x3 

もっず楜しみたしょう。 自分でURLを返すものを収瞮させたす

 $ curl http://localhost:8080/debug/pprof/profile?seconds=5 -o /tmp/cpu.log 

go tool test -cpuprofileたたはStartCPUProfileを䜿甚するず、/ tmp / cpu.log内で同じバむナリデヌタが返されるこずが わかりたす。 このバむナリファむルに文字列コマンドを「蚭定」し、関数名やいわゆる文字が内郚にないこずを理解したす。

 $ strings /tmp/cpu.log | grep cpuhogger 

では、最初の堎合、バむナリなしでpprofを起動したずき、関数名はどこで取埗されたしたか net / http / pprofをむンポヌトするず、別のURL / debug / pprof / symbolが远加され 、関数のアドレスに名前が返されたす。 このURLを照䌚するこずにより、pprofコマンドは関数名を取埗したす。

ただし、このURLは関数の゜ヌスコヌドを返さず、逆アセンブルもしたせん。 分解するには、バむナリが必芁です。゜ヌスコヌドには、ディスク䞊のバむナリず゜ヌスコヌドの䞡方が必芁です。

泚意送信されたバむナリず゜ヌスコヌドは実行䞭のものでなければなりたせん。 そうしないず、期埅するデヌタがたったく埗られない可胜性があり、存圚しない問題を探したす。

pprofはどのように機胜したすか


奜奇心から、pprofがどのように機胜し、䜿甚するアプロヌチにどのような欠点があるかを正確に芋おみたしょう。

倚くのプログラムの動䜜を同時に保蚌するために、最新のデスクトップおよびサヌバヌオペレヌティングシステムは、いわゆるプリ゚ンプティブマルチタスクを実装しおいたす。 プログラムには、特定の期間ず、それが機胜する特定のプロセッサが割り圓おられたす。 この時間が経過するず、OSはプログラムの代わりになり、䜜業の準備ができおいれば、代わりに別のプログラムを起動したす。

しかし、割り蟌み機胜はどのくらい正確に実装されおいたすか 結局のずころ、OSはほが同じプログラムです。 問題は、OSが特定の呚波数の信号をアむロンに送信するように芁求し、この信号にプロセッサを割り圓おるこずです。 シグナルが到着するず、プロセッサは珟圚実行䞭のすべおを停止し、指定されたハンドラヌを開始したす。 このハンドラでは、OSが珟圚のプロセスを暪取りしたり、別のプロセスに眮き換えたりする堎合がありたす。

Goプロファむラヌも同じように機胜したす。 Goランタむムは、特定の頻床でシグナルman setitimerを送信するようOSに芁求し、このシグナルにハンドラヌを割り圓おたす。 ハンドラヌは、すべおのゎルヌチン英語のゎルヌチン のスタックトレヌス、いく぀かの远加情報を取埗し、それをバッファヌに曞き蟌んで終了したす。

以前のバヌゞョンのOS Xの問題が関連しおいるのは、特定のスレッドにシグナルを配信するプロセスのバグです。

このアプロヌチの欠点は䜕ですか


もちろん、䞻な利点は、Goランタむムがその内郚構造に関する完党な情報を持っおいるこずです。 たずえば、倖郚ツヌルは、デフォルトではゎルヌチンに぀いお䜕も知りたせん。 圌らにずっおは、プロセスずスレッドのみです。

システムプロファむラヌ


組み蟌みのGoプロファむラがどのように機胜するかを調べたした。 暙準のperfおよびSystemTap Linuxプロファむラヌがどのように適甚できるかを芋おみたしょう。

この蚘事の最初のプログラムを取り䞊げたす。ベンチマヌクから、無限に機胜する通垞のプログラムに倉えたす。

 package main import ( "regexp" "strings" ) var haystack = `Lorem ipsum dolor sit amet, consectetur adipiscing [...] Vivamus vitae nulla posuere, pellentesque quam posuere` func UsingSubstring() bool { found := strings.Contains(haystack, "auctor") return found } func UsingRegex() bool { found, _ := regexp.MatchString("auctor", haystack) return found } func main() { go func() { for { UsingSubstring() } }() for { UsingRegex() } } 

システムタップ


SystemTapは非垞に匷力なプロファむラヌで、小さな蚀語のプログラムを擬䌌蚀語で䜜成できたす。 その埌、このプログラムは自動的にCに倉換され、Linuxカヌネルモゞュヌルずしおアセンブルされ、ロヌド、実行、アンロヌドされたす。

SystemTapが関数を認識しおいるかどうかを芋おみたしょう。

 $ stap -l 'process("systemtap").function("main.*")' process("systemtap").function("main.UsingRegex@main.go:16") process("systemtap").function("main.UsingSubstring@main.go:11") process("systemtap").function("main.init@main.go:32") process("systemtap").function("main.main.func1@main.go:22") process("systemtap").function("main.main@main.go:21") 

圌は芋たす。 予想どおり、すべおの関数にはmainずいうプレフィックスが付いおいたす。

2぀の関数の動䜜時間を枬定し、結果をヒストグラムで衚瀺しおみたしょう。

SystemTap蚀語で次の簡単なスクリプトを䜜成したす。 関数の入り口の時間を蚘憶し、出口の時間を枬定し、差を蚈算しお保存したす。 ゞョブの完了埌、圌はこの情報を印刷したす。

 global etime global intervals probe $1.call { etime = gettimeofday_ns() } probe $1.return { intervals <<< (gettimeofday_ns() - etime)/1000 } probe end { printf("Duration min:%dus avg:%dus max:%dus count:%d\n", @min(intervals), @avg(intervals), @max(intervals), @count(intervals)) printf("Duration (us):\n") print(@hist_log(intervals)); printf("\n") } 

あるタヌミナルでプログラムを実行し、別のタヌミナルでホチキスで留めたす。

 $ sudo stap main.stap 'process("systemtap").function("main.UsingSubstring")' ^CDuration min:0us avg:1us max:586us count:1628362 Duration (us): value |-------------------------------------------------- count 0 | 10 1 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1443040 2 |@@@@@ 173089 4 | 6982 8 | 4321 16 | 631 32 | 197 64 | 74 128 | 13 256 | 4 512 | 1 1024 | 0 2048 | 0 

結果は埗られたしたが、プログラムは非垞にうたく機胜したため、゚ラヌが発生したした。

 $ ./systemtap runtime: unexpected return pc for main.UsingSubstring called from 0x7fffffffe000 fatal error: unknown caller pc runtime stack: runtime.throw(0x494e40, 0x11) /home/marko/go/src/runtime/panic.go:566 +0x8b runtime.gentraceback(0xffffffffffffffff, 0xc8200337a8, 0x0, 0xc820001d40, 0x0, 0x0, 0x7fffffff, 0x7fff2fa88030, 0x0, 0x0, ...) /home/marko/go/src/runtime/traceback.go:311 +0x138c runtime.scanstack(0xc820001d40) /home/marko/go/src/runtime/mgcmark.go:755 +0x249 runtime.scang(0xc820001d40) /home/marko/go/src/runtime/proc.go:836 +0x132 runtime.markroot.func1() /home/marko/go/src/runtime/mgcmark.go:234 +0x55 runtime.systemstack(0x4e4f00) /home/marko/go/src/runtime/asm_amd64.s:298 +0x79 runtime.mstart() /home/marko/go/src/runtime/proc.go:1087 

go-nutsでそれに関するスレッドを芋぀けたしたが、ただ解決策はありたせん。 どうやら、SystemTapがプログラムコヌドを倉曎しお関数をむンタヌセプトする方法は、GCでスタックトレヌスを受信するずきのGoランタむムを奜たないようです。

䟋倖を凊理するずきに、C ++でも同じ問題が発生したす。 Uretprobesは完璧ではありたせん。

さお、しかし、.returnサンプルを䜿甚しない堎合、倧䞈倫ですか やっおみたしょう。

乱数を取埗しお文字列に倉換し、バッファに栌玍するプログラムを次に瀺したす。

 package main import ( "bytes" "fmt" "math/rand" "time" ) func ToString(number int) string { return fmt.Sprintf("%d", number) } func main() { r := rand.New(rand.NewSource(time.Now().UnixNano())) var buf bytes.Buffer for i := 0; i < 1000; i++ { value := r.Int() % 1000 value = value - 500 buf.WriteString(ToString(value)) } } 

文字列に倉換する数倀の分垃を䜜成するスクリプトを䜜成したしょう。

 global intervals probe process("systemtap02").function("main.ToString").call { intervals <<< $number } probe end { printf("Variables min:%dus avg:%dus max:%dus count:%d\n", @min(intervals), @avg(intervals), @max(intervals), @count(intervals)) printf("Variables:\n") print(@hist_log(intervals)); printf("\n") } 

前のプログラムずは異なり、プログラムは.returnプロヌブを䜿甚したせんが、匕数番号を取り、䜿甚したす。

実行しお䜕が起こったのかを芋おください

 $ sudo stap main.stap -c ./systemtap02 Variables min:-499us avg:8us max:497us count:1000 Variables: value |-------------------------------------------------- count -1024 | 0 -512 | 0 -256 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 249 -128 |@@@@@@@@@@@@@@@@@@@@ 121 -64 |@@@@@@@@@@ 60 -32 |@@@@@@ 36 -16 |@@ 12 -8 |@ 8 -4 | 5 -2 | 3 -1 | 2 0 | 2 1 | 2 2 | 3 4 |@ 7 8 | 4 16 |@@@ 20 32 |@@@@@ 33 64 |@@@@@@@ 44 128 |@@@@@@@@@@@@@@@@@@ 110 256 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 279 512 | 0 1024 | 0 

配垃のスケゞュヌルがきれいになりたした。

性胜


perfナヌティリティずperf_eventsサブシステムは珟圚、Linuxのデフォルトのプロファむラヌです。 ゜ヌスず開発は䞻にカヌネルリポゞトリに行き、カヌネルず同等になりたす。

perf topは、topず同様に、最もホットなコヌドをリアルタむムで衚瀺するコマンドです。 テストプログラムを実行し、perf topが衚瀺するものを確認したす。

 $ sudo perf top -p $(pidof systemtap) 




すべおがうたくいくようで、゜ヌスおよびマシンコヌドを䜿甚した泚釈も機胜したす。



それでは、Brendan Greggによっお普及した、いわゆるFlameGraphを構築しおみたしょう。 Brendanは珟圚Netflixで働いおおり、Linuxの革新的なプロファむリングの䞻芁なプロモヌタヌおよび「゚ンゞン」の1぀です。

再床、プログラムを実行し、10秒以内にスタックトレヌスをファむルに収集したす。

 $ sudo perf record -F 99 -g -p $(pidof systemtap) -- sleep 10 [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.149 MB perf.data (1719 samples) ] 

Brendanのナヌティリティを䜿甚しお、パフォヌマンスデヌタをFlameGraphに倉換したす。

 $ sudo perf script | ~/tmp/FlameGraph/stackcollapse-perf.pl > out.perf-folded $ ~/tmp/FlameGraph/flamegraph.pl out.perf-folded > perf-kernel.svg 

そしお、ここに私たちが埗たものがありたす



ご芧のずおり、Goの組み蟌みプロファむラヌずは異なり、ここにはカヌネルスタックトレヌスもありたす。

蚘憶


CたたはC ++でプログラミングした堎合、メモリ䜿甚量をどのようにプロファむルしたすか

C / C ++の䞖界には、メモリを䜿甚するずきに゚ラヌを怜玢するように蚭蚈されたナヌティリティであるValgrindがありたすリヌク、配列の境界倖ぞの移動、すでに解攟されたメモリの䜿甚など。 これはすべお必芁ありたせん。なぜなら Goでは、このような問題がないこずが保蚌されおいたすもちろんcgoを䜿甚する堎合を陀く。

しかし、Valgrindは、組み蟌みのMassifサブシステムを䜿甚しお、メモリ消費を䟿利なタむムスケゞュヌルの圢匏で衚瀺する方法も知っおいたす。

単玔に割り圓おおから20 MiBのメモリを解攟する単玔なCプログラムを䜿甚する堎合

 #include <stdlib.h> #include <unistd.h> #include <string.h> int main() { const size_t MB = 1024*1024; const unsigned count = 20; char **buf = calloc(count, sizeof(*buf)); for (unsigned i = 0; i < count; i++) { buf[i] = calloc(1, MB); memset(buf[i], 0xFF, MB); sleep(1); } for (unsigned i = 0; i < count; i++) { free(buf[i]); sleep(1); } free(buf); } 

Massifの䞋で実行するず、メモリの割り圓おが開始された堎所のスタックトレヌスでこのグラフのようなものが埗られたす。

 -------------------------------------------------------------------------------- Command:            ./main Massif arguments:   --pages-as-heap=yes --time-unit=ms ms_print arguments: massif.out.15091 --------------------------------------------------------------------------------   MB 26.20^                                   ::    |                                 ::: #    |                               @@: : #::    |                             ::@ : : #: ::    |                         ::::: @ : : #: : ::::    |                        :: : : @ : : #: : : : ::    |                      :::: : : @ : : #: : : : : :    |                  ::::: :: : : @ : : #: : : : : :::::    |                ::: : : :: : : @ : : #: : : : : :: : @@    |              ::: : : : :: : : @ : : #: : : : : :: : @ ::    |           ::@: : : : : :: : : @ : : #: : : : : :: : @ : :::    |         ::: @: : : : : :: : : @ : : #: : : : : :: : @ : : :::    |       ::: : @: : : : : :: : : @ : : #: : : : : :: : @ : : :: ::    |     ::: : : @: : : : : :: : : @ : : #: : : : : :: : @ : : :: : ::    | ::::: : : : @: : : : : :: : : @ : : #: : : : : :: : @ : : :: : : ::::    |:: : : : : : @: : : : : :: : : @ : : #: : : : : :: : @ : : :: : : : : :    |@: : : : : : @: : : : : :: : : @ : : #: : : : : :: : @ : : :: : : : : :@    |@: : : : : : @: : : : : :: : : @ : : #: : : : : :: : @ : : :: : : : : :@    |@: : : : : : @: : : : : :: : : @ : : #: : : : : :: : @ : : :: : : : : :@    |@: : : : : : @: : : : : :: : : @ : : #: : : : : :: : @ : : :: : : : : :@  0 +----------------------------------------------------------------------->s    0                                                                   39.13 Number of snapshots: 66 Detailed snapshots: [9, 10, 20, 22, 32, 42, 45 (peak), 55, 65] 

Massifは、メモリを操䜜するための䞻芁な関数malloc、calloc、realloc、memalign、new、new []を独自に再定矩するように機胜したす。

Goはこれらの機胜を䜿甚したせん。 Go゜ヌスには独自のアロケヌタヌがあり、これはmmapたたはsbrkシステムコヌルを䜿甚しおOSからメモリを盎接芁求し、すでにそれを小さな断片に分割しおいたす。



Valgrindは特別なコマンドラむンパラメヌタヌで芁求するずmmap / sbrkをキャッチできたすが、それでも圹に立たないのは、第1に、これらの最高の割り圓おずリリヌスが衚瀺されず、第2に、理解できないためです。参照されなくなったメモリず、ただ「ラむブ」であるメモリ。

C / C ++の䞖界の他の䞀般的なナヌティリティもほずんど圹に立たない それらのほずんどは同様の方法で動䜜したす。 メモリの割り圓おず解攟の機胜を傍受するこずにより。

基本的に2぀のオプションがありたす。


Goは、定期的にメモリ割り圓おに関する情報を収集できたす。 この頻床は手動で蚭定できたすが、デフォルトでは、割り圓おられたメモリ512キロバむトに぀き1回です。

い぀ものように、䟋を芋おみたしょう。

䟋


プロセッサのプロファむリングず同様に、メモリのプロファむリングは、 go test 、 runtime.MemProfileぞの盎接呌び出し、たたはnet / http / pprofパッケヌゞを䜿甚しお開始できたす 。 今回は、最埌のオプションを䜿甚したしょう。

そのため、ここでは、ゎルヌチンの1぀で絶えず配列を割り圓おお別の配列に栌玍するプログラムを瀺したす。他のゎルヌチンでは同じこずを行いたすが、配列の配列を定期的に「忘れ」たす。

 package main import ( "net/http" _ "net/http/pprof" "time" ) func allocAndKeep() { var b [][]byte for { b = append(b, make([]byte, 1024)) time.Sleep(time.Millisecond) } } func allocAndLeave() { var b [][]byte for { b = append(b, make([]byte, 1024)) if len(b) == 20 { b = nil } time.Sleep(time.Millisecond) } } func main() { go allocAndKeep() go allocAndLeave() http.ListenAndServe("0.0.0.0:8080", nil) } 

぀たり , , , .

, .

go tool pprof , :


, — .

, inuse allocAndKeep(), alloc :

 $ go tool pprof -inuse_space memtest http://localhost:8080/debug/pprof/heap Fetching profile from http://localhost:8080/debug/pprof/heap Saved profile in /home/marko/pprof/pprof.memtest.localhost:8080.inuse_objects.inuse_space.005.pb.gz Entering interactive mode (type "help" for commands) (pprof) top 15.36MB of 15.36MB total ( 100%) Dropped 2 nodes (cum <= 0.08MB) flat flat% sum% cum cum% 15.36MB 100% 100% 15.36MB 100% main.allocAndKeep 0 0% 100% 15.36MB 100% runtime.goexit $ go tool pprof -alloc_space memtest http://localhost:8080/debug/pprof/heap Fetching profile from http://localhost:8080/debug/pprof/heap Saved profile in /home/marko/pprof/pprof.memtest.localhost:8080.alloc_objects.alloc_space.008.pb.gz Entering interactive mode (type "help" for commands) (pprof) top 54.49MB of 54.49MB total ( 100%) Dropped 8 nodes (cum <= 0.27MB) flat flat% sum% cum cum% 27.97MB 51.33% 51.33% 29.47MB 54.08% main.allocAndKeep 23.52MB 43.17% 94.49% 25.02MB 45.92% main.allocAndLeave 3MB 5.51% 100% 3MB 5.51% time.Sleep 0 0% 100% 54.49MB 100% runtime.goexit 

. , Sleep() - . .

 (pprof) list time.Sleep Total: 54.49MB ROUTINE ======================== time.Sleep in /home/marko/go/src/runtime/time.go 3MB 3MB (flat, cum) 5.51% of Total . . 48:func timeSleep(ns int64) { . . 49: if ns <= 0 { . . 50: return . . 51: } . . 52: 3MB 3MB 53: t := new(timer) . . 54: t.when = nanotime() + ns . . 55: tf = goroutineReady . . 56: t.arg = getg() . . 57: lock(&timers.lock) . . 58: addtimerLocked(t) 

, , time.Sleep() new().

(1)


, , — . , .

, .

 package printtest import ( "bytes" "fmt" "testing" ) func BenchmarkPrint(b *testing.B) { var buf bytes.Buffer var s string = "test string" for i := 0; i < bN; i++ { buf.Reset() fmt.Fprintf(&buf, "string is: %s", s) } } 

fmt.Fprintf() .
ᅩᅩ-benchmem test .

 $ go test -bench=. -benchmem testing: warning: no tests to run BenchmarkPrint-8 10000000 128 ns/op 16 B/op 1 allocs/op PASS ok github.com/mkevac/converttest 1.420s 

, 1 16 . ?

:

 $ go test -bench=. -memprofile=mem.out -memprofilerate=1 

memprofilerate , . , . , . .

:

 $ go tool pprof -alloc_space converttest.test mem.out (pprof) top 15.41MB of 15.48MB total (99.59%) Dropped 73 nodes (cum <= 0.08MB)     flat  flat%   sum%        cum   cum%  15.41MB 99.59% 99.59%    15.43MB 99.67%  github.com/mkevac/converttest.BenchmarkPrint        0     0% 99.59%    15.47MB 99.93%  runtime.goexit        0     0% 99.59%    15.42MB 99.66%  testing.(*B).launch        0     0% 99.59%    15.43MB 99.67%  testing.(*B).runN 

, 15 MiB . どこ

 (pprof) list BenchmarkPrint Total: 15.48MB ROUTINE ======================== github.com/mkevac/converttest.BenchmarkPrint in /home/marko/goprojects/src/github.com/mkevac/converttest/convert_test.go 15.41MB 15.43MB (flat, cum) 99.67% of Total . . 9:func BenchmarkPrint(b *testing.B) { . . 10: var buf bytes.Buffer . . 11: var s string = "test string" . . 12: for i := 0; i < bN; i++ { . . 13: buf.Reset() 15.41MB 15.43MB 14: fmt.Fprintf(&buf, "string is: %s", s) . . 15: } . . 16:} 

fmt.Fprintf(). いいね そしおどこ

 (pprof) list fmt.Fprintf Total: 15.48MB ROUTINE ======================== fmt.Fprintf in /home/marko/go/src/fmt/print.go 0 12.02kB (flat, cum) 0.076% of Total . . 175:// These routines end in 'f' and take a format string. . . 176: . . 177:// Fprintf formats according to a format specifier and writes to w. . . 178:// It returns the number of bytes written and any write error encountered. . . 179:func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { . 11.55kB 180: p := newPrinter() . 480B 181: p.doPrintf(format, a) . . 182: n, err = w.Write(p.buf) . . 183: p.free() . . 184: return . . 185:} . . 186: 

. , , 
 - . 15 , 12 . - .

:

          .          .     466edb: CALL bytes.(*Buffer).Reset(SB)        .          .     466ee0: LEAQ 0x98b6b(IP), AX        .          .     466ee7: MOVQ AX, 0x70(SP)        .          .     466eec: MOVQ $0xb, 0x78(SP)        .          .     466ef5: MOVQ $0x0, 0x60(SP)        .          .     466efe: MOVQ $0x0, 0x68(SP)        .          .     466f07: LEAQ 0x70d92(IP), AX        .          .     466f0e: MOVQ AX, 0(SP)        .          .     466f12: LEAQ 0x70(SP), AX        .          .     466f17: MOVQ AX, 0x8(SP)        .          .     466f1c: MOVQ $0x0, 0x10(SP)  15.41MB    15.41MB     466f25: CALL runtime.convT2E(SB)        .          .     466f2a: MOVQ 0x18(SP), AX        .          .     466f2f: MOVQ 0x20(SP), CX        .          .     466f34: MOVQ AX, 0x60(SP)        .          .     466f39: MOVQ CX, 0x68(SP)        .          .     466f3e: LEAQ 0x10b35b(IP), AX        .          .     466f45: MOVQ AX, 0(SP)        .          .     466f49: MOVQ 0x58(SP), AX        .          .     466f4e: MOVQ AX, 0x8(SP)        .          .     466f53: LEAQ 0x99046(IP), CX        .          .     466f5a: MOVQ CX, 0x10(SP)        .          .     466f5f: MOVQ $0xd, 0x18(SP)        .          .     466f68: LEAQ 0x60(SP), CX        .          .     466f6d: MOVQ CX, 0x20(SP)        .          .     466f72: MOVQ $0x1, 0x28(SP)        .          .     466f7b: MOVQ $0x1, 0x30(SP)        .    12.02kB     466f84: CALL fmt.Fprintf(SB) 

- runtime.convT2E . これは䜕ですか

, fmt.Fprintf():

 func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) 

, . , , void* .

«» , .. . , ? なんで Go.

Go , string, chan, func, slice, interface .. .

, string, .. :



— 16 . 8 — , , , , 8 — .

interface. Interface 8- .



8 — , , 8 — .



 var s string = "marko" var a interface{} = &s 

, 8 .

. , :

 var s string = "marko" var a interface{} = s 

Go runtime.convT2E.

:



16 go test.

«» .
fmt.Fprintf :

 package main import ( "bytes" "testing" ) func BenchmarkPrint(b *testing.B) { var buf bytes.Buffer var s string = "test string" for i := 0; i < bN; i++ { buf.Reset() buf.WriteString("string is: ") buf.WriteString(s) } } 

0 :

 $ go test -bench=BenchmarkPrint -benchmem testing: warning: no tests to run BenchmarkPrint-8 50000000 27.5 ns/op 0 B/op 0 allocs/op PASS ok github.com/mkevac/converttest01 1.413s 

4 .

(2)


«» , cgo. ( char * ) — , , . , .

Go, , . — , .



 package main import ( "fmt" ) func main() { var array = []byte{'m', 'a', 'r', 'k', 'o'} if string(array) == "marko" { fmt.Println("equal") } } 

, . . git-blame , . , , (do not escapes to heap), , .

. , , , , . runtime. , , , , 2010 , , .

.

- , Go- , escapes to heap.

:

 package main import ( "bytes" "testing" "unsafe" ) var s string func BenchmarkConvert(b *testing.B) { var buf bytes.Buffer var array = []byte{'m', 'a', 'r', 'k', 'o', 0} for i := 0; i < bN; i++ { buf.Reset() s = string(array) buf.WriteString(s) } } 

 $ go test -bench=. -benchmem testing: warning: no tests to run BenchmarkConvert-8 30000000 42.1 ns/op 8 B/op 1 allocs/op 

reflect unsafe.

 func BytesToString(b []byte) string { bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) sh := reflect.StringHeader{bh.Data, bh.Len} return *(*string)(unsafe.Pointer(&sh)) } func BenchmarkNoConvert(b *testing.B) { var buf bytes.Buffer var array = []byte{'m', 'a', 'r', 'k', 'o', 0} for i := 0; i < bN; i++ { buf.Reset() s = BytesToString(array) buf.WriteString(s) } } 

.

 $ go test -bench=. -benchmem testing: warning: no tests to run BenchmarkConvert-8 30000000 44.5 ns/op 8 B/op 1 allocs/op BenchmarkNoConvert-8 100000000 19.2 ns/op 0 B/op 0 allocs/op PASS ok github.com/mkevac/bytetostring 3.332s 


, Go — . , Go runtime , , , : , , , .. Go runtime/trace.go .

— . , Chrome -.

, , .

䟋


- GC- debugcharts.



runtime.ReadMemStats() , GC- .

, « », debugcharts.

 package main import ( "net/http" _ "net/http/pprof" "time" _ "github.com/mkevac/debugcharts" ) func CPUHogger() { var acc uint64 t := time.Tick(2 * time.Second) for { select { case <-t: time.Sleep(50 * time.Millisecond) default: acc++ } } } func main() { go CPUHogger() go CPUHogger() http.ListenAndServe("0.0.0.0:8181", nil) } 

trace, , .

10 . : runtime - , 1-3 . , Chrome JavaScript .

 curl http://localhost:8181/debug/pprof/trace?seconds=10 -o trace.out 

go tool trace , :

 go tool trace -http "0.0.0.0:8080" ./tracetest trace.out 

, :



:



, , , . , , .



, , , , :



, 4 , , 2 50 , . , - , , , debugcharts. , .

, debugcharts:



- . Debugcharts , , , . .

, , , proc stop proc start .

, , debugcharts . latency .

runtime.ReadMemStats() , .

 180 // ReadMemStats populates m with memory allocator statistics. 181 func ReadMemStats(m *MemStats) { 182         stopTheWorld("read mem stats") 183 184         systemstack(func() { 185                 readmemstats_m(m) 186         }) 187 188         startTheWorld() 189 } 

. .

, debugcharts .

おわりに


, , .



, Go:



, Go , , , .

Go, , . perf SystemTap. .

, — , , , — .

, . Stay curious!

, C/C++

Source: https://habr.com/ru/post/J301990/


All Articles