2016-10-18 5 views
1

異なるヒープオブジェクト数に対して、最大STW GCの休止時間をベンチマークしようとしています。私はGODEBUG=gctrace=1でこれを実行して、私のマシン上で出力されているなぜ「同時」Go GCフェーズが世界的に停止しているように見えますか?

package main 

type message []byte 

type channel map[int]message 

const (
    windowSize = 200000 
    msgCount = 1000000 
) 

func mkMessage(n int) message { 
    m := make(message, 1024) 
    for i := range m { 
     m[i] = byte(n) 
    } 
    return m 
} 

func pushMsg(c *channel, highID int) { 
    lowID := highID - windowSize 
    m := mkMessage(highID) 
    (*c)[highID] = m 
    if lowID >= 0 { 
     delete(*c, lowID) 
    } 
} 

func main() { 
    c := make(channel) 
    for i := 0; i < msgCount; i++ { 
     pushMsg(&c, i) 
    } 
} 

gc 1 @0.004s 2%: 0.007+0.44+0.032 ms clock, 0.029+0.22/0.20/0.28+0.12 ms cpu, 4->4->3 MB, 5 MB goal, 4 P 
gc 2 @0.009s 3%: 0.007+0.64+0.042 ms clock, 0.030+0/0.53/0.18+0.17 ms cpu, 7->7->7 MB, 8 MB goal, 4 P 
gc 3 @0.019s 1%: 0.007+0.99+0.037 ms clock, 0.031+0/0.13/1.0+0.14 ms cpu, 13->13->13 MB, 14 MB goal, 4 P 
gc 4 @0.044s 2%: 0.009+2.3+0.032 ms clock, 0.039+0/2.3/0.30+0.13 ms cpu, 25->25->25 MB, 26 MB goal, 4 P 
gc 5 @0.081s 1%: 0.009+9.2+0.082 ms clock, 0.039+0/0.32/9.7+0.32 ms cpu, 49->49->48 MB, 50 MB goal, 4 P 
gc 6 @0.162s 0%: 0.020+10+0.078 ms clock, 0.082+0/0.28/11+0.31 ms cpu, 93->93->91 MB, 96 MB goal, 4 P 
gc 7 @0.289s 0%: 0.020+27+0.092 ms clock, 0.080+0/0.95/28+0.37 ms cpu, 178->178->173 MB, 182 MB goal, 4 P 
gc 8 @0.557s 1%: 0.023+38+0.086 ms clock, 0.092+0/38/10+0.34 ms cpu, 337->339->209 MB, 346 MB goal, 4 P 
gc 9 @0.844s 1%: 0.008+40+0.077 ms clock, 0.032+0/5.6/46+0.30 ms cpu, 407->409->211 MB, 418 MB goal, 4 P 
gc 10 @1.100s 1%: 0.009+43+0.047 ms clock, 0.036+0/6.6/50+0.19 ms cpu, 411->414->212 MB, 422 MB goal, 4 P 
gc 11 @1.378s 1%: 0.008+45+0.093 ms clock, 0.033+0/6.5/52+0.37 ms cpu, 414->417->213 MB, 425 MB goal, 4 P 

は、上記のリンクを参照してください。これを行うには、私はmapからメッセージをプッシュしてポップシンプルなベンチマークを書かれていますこの出力に関するドキュメント

行くの私のバージョンは次のとおりです。以上の結果から

$ go version 
go version go1.7.1 darwin/amd64 

、最長の壁時計STWの一時停止時間が0.093 msです。すばらしいです!

は、しかし、健全性チェックとして、私は手動でも、それが

start := time.Now() 
m := mkMessage(highID) 
elapsed := time.Since(start) 

mkMessageをラップすることによって作成するために、新しいmessageを取り、最も遅いelapsed時間を印刷どのくらいタイムアウトしました。私がこれを得る時間は38.573036msでした!

これはおそらく並行マーク/スキャンフェーズで、そして「アイドルGC時間」と、特に壁時計時間と強く相関しているので、私は即座に不審でした。

私の質問は:なぜGCのこのおそらく同時相はミューテータをブロックするように見えるのでしょうか?

GCを定期的に実行すると、手動で計算された一時停止時間が< 1msになります。そのため、ライブ以外のヒープオブジェクトの制限があるようです。もしそうなら、私はその限界が何であるか分からず、なぜGCの同時フェーズがミューテータをブロックするように見えるのか?

+2

特定の回答が必要な場合、goメーリングリストはおそらくより適切なフォーラムです(まだ行っていない場合は、現在のmasterブランチでこれをテストする必要があります) – JimB

+0

これを見ると、ここでは大まかな相関が見られますが、小さな入力でしか見られません。 'mkMessage'の大部分の時間は、スライスを変更するのではなく、GC中の割り当てを待つのに費やされます。ポインタの数を1桁増加させると、それに応じて同時フェーズで費やされる時間が増えますが、スライスの割り当てや変更に要する時間は大幅に増加しません。 – JimB

+0

これには巨大なマップがあり、一時停止の原因となるバグがあります。インクリメンタルな「ミューテータアシスト」 - コードが中断されてわずかな作業が行われ、マイクロ秒単位で実行されることがあり、マップ全体をスキャンすることがあります。 https://groups.google.com/forum/#!topic/golang-codereviews/kIqdlqEZ47M、https://github.com/golang/go/issues/16432、https://github.com/golang/goをご覧ください。/issues/14812、およびhttps://github.com/golang/go/issues/9477を参照してください。 Goのヒントがうまくいくかもしれません。古いバージョンの場合は1.7です。 – twotwotwo

答えて

1

通常、新しいGC割り当てで一定量の領域が使用可能になった時点で、GCの同時実行が開始されます。パスが完了する前に発生するすべての割り当てを処理するのに十分なスペースがある場合、アプリケーションがGCで待機する時間は最小限に抑えられます。ただし、割り当て量が使用可能な領域を超えた場合、新しい割り当て要求はGCがより多くの領域を解放するのを待たなければなりません。ほとんどのコンカレントGCシステムは、彼らが進行中にプログラムを実行できるにもかかわらず、明確なサイクルを持って

注意。 GCサイクルの大部分は、参照が存在するすべてのオブジェクトを識別するために費やされ、個々のオブジェクトに参照が存在しないことを識別する唯一の方法は、にあるすべての参照を識別することです。。その結果、GCサイクルによって解放されるすべてのものが一度に解放され、その間に何も解放されません。

+0

あなたが言っていることを理解しているかどうかを見てみましょう。 GCには、新しい割り当てに使用できる一定量のメモリがあります。これを 's'と呼んでください。私の例では、より多くのオブジェクトが作成されると、 's'が減少します。 's'がある値に達すると、GCが起動します。 's'が0になると、新しいオブジェクトの作成要求がブロックされます。 GCが再度実行されるか、またはOSからより多くのメモリが要求されると、要求は続行されます。それは私にとって理にかなっていますが、もし私がそれに従っていなければ私に知らせてください。 –

+0

@WillSewell:リクエストがGCが実行されるまで待つ必要がなく、サイクルが完了するまで待つ必要があることを除けば、それはかなりです。特定のメモリを解放できるかどうかを調べるには、システムはすべてのライブオブジェクトをスキャンする必要がありますが、すべてのライブオブジェクトを1回スキャンすると、解放できるすべてのメモリがシステムによって識別されます。 – supercat

0

私のコメントよりももっと多くの可視性(および詳細)を与えるには、その動作はGo bug #9477に記載されているものと一致し、Go changelist 23540で修正されているようです。何が起こる

スレッドがメモリを割り当てたときに、ガベージコレクタはそれを行うにはわずかなメモリスキャン作業を与えるかもしれない、です。これは「ミューテータアシスト」と呼ばれています。しかし以前は、マップをスキャンする作業が行われていましたが、地図が十分に長くなっているために長い時間がかかることがありました。質問やコメントで報告されていることと一貫して、一時停止は、地図を使用している間ではなく、何らかの割り当てが行われたときに発生し、停止の一部ではないためGCの一時停止の統計には表示されません - 世界の段階。

私が書いているように、まだリリースされていない修正は、典型的な最新のサーバーハードウェアで約100マイクロ秒を要しないように、単一のミューテータアシスタントがスキャンするメモリ量を制限することでした。この問題が発生した場合は、修正が適用されているgithub.com/golang/goリポジトリから最新のものを試してみることをおすすめします。 (それ以来、そこにもanother big pause-reduction changeが1.7です。)まだ問題があり、説明を見つけることができなかったら、私はgolang-nutsに行きます。

関連する問題