次のコラム golang チュートリアル では、Golang GC ガベージ コレクションのメカニズムについて詳しく説明します。困っている友人の役に立てば幸いです。
#まとめgo In の実際の使い方この言語を学習する過程で、一見奇妙なメモリ使用現象に遭遇したため、Go 言語のガベージ コレクション モデルについて調査することにしました。この記事はその研究結果をまとめたものです。 ガベージ コレクションとは何ですか? かつて、メモリ管理はアプリケーションを開発するプログラマにとって大きな問題でした。従来のシステムレベルのプログラミング言語 (主に C/C) では、プログラマはメモリを注意深く管理し、メモリの適用と解放を制御する必要がありました。注意しないとメモリ リークが発生する可能性があり、この種の問題は見つけて見つけるのが難しく、開発者にとっては常に悪夢でした。この頭痛の問題を解決するにはどうすればよいでしょうか?以前は、実際に多数の観察を行った結果、オブジェクト指向プログラミング言語では、ほとんどのオブジェクトのライフ サイクルが非常に短いことがわかりました。世代別コレクションの基本的な考え方は、ヒープを世代と呼ばれる 2 つ以上のスペースに分割することです。新しく作成されたオブジェクトは、いわゆる若い世代に格納されます (一般に、若い世代のサイズは古い世代よりもはるかに小さくなります)。ガベージ コレクションが繰り返し実行されると、ライフ サイクルの長いオブジェクトが昇格されます (プロモーション) )古い世代へ。そこで、新世代ガベージ コレクションと旧世代ガベージ コレクションという 2 つの異なるガベージ コレクション方法が登場し、それぞれの空間内のオブジェクトに対してガベージ コレクションを実行します。新世代のガベージコレクションの速度は旧世代に比べて数桁高速であり、ガベージコレクションの頻度が高くなっても実行効率は旧世代より優れています。これは、ほとんどのオブジェクトのライフ サイクルが非常に短いためであり、古い世代に昇格する必要はまったくありません。
Go 言語のガベージ コレクションは、通常、古典的なマーク アンド スイープ アルゴリズムを使用します。
チームは、go 言語を練習するときに、メモリの問題 (主に gc) という最も困難な問題にも遭遇しました。遭遇した問題と経験は次のとおりです。要約すると、誰でもコミュニケーションやディスカッションを歓迎します。
この問題は、バックグラウンド サービスのストレス テストを実施したときに発見されました。バックグラウンド サービスにアクセスするための多数のユーザー リクエストをシミュレートしました。現時点では、各サービス モジュールでメモリ使用量の大幅な増加が観察されます。ただし、ストレス テストを停止しても、メモリ使用量は大幅に減少しませんでした。 gprof などのさまざまな方法を使用して問題を特定するのに長い時間がかかりましたが、依然として原因は見つかりませんでした。最終的に、この時点ではそれが正常であることがわかりました...主な理由は 2 つあります。
まず、go のガベージ コレクションにはトリガーのしきい値があり、メモリ使用量が増加するたびに徐々に増加していきます (たとえば、最初のしきい値は 10MB、次は 20MB、次は 40MB になります)。 ..) 、gc go が長期間トリガーされなかった場合、アクティブに 1 回 (2 分) トリガーされます。ピーク時にメモリ使用量が増加した後は、メモリの適用を継続しない限り、しきい値に基づいて gc をトリガーすることはほとんど不可能になり、アクティブな gc が開始されるまで最大 2 分間待ってから gc をトリガーする必要があります。
2 番目の理由は、Go 言語がメモリをシステムに返すとき、そのメモリはもう必要ないのでリサイクルできることをシステムに伝えるだけであり、同時にオペレーティング システムは「遅延」戦略。すぐにはリサイクルしませんが、システム メモリが不足するまで待ってからリサイクルを開始します。そのため、プログラムがメモリを再申請するときに、非常に高速な割り当て速度を得ることができます。
ユーザーがイベントに応答する必要があるバックエンド プログラムにとって、golang の gc 中にパートタイムで世界を停止することは悪夢です。上記の紹介によると、上記の改善が完了した後、go のバージョン 1.5 の gc パフォーマンスは大幅に向上しますが、すべてのガベージ コレクション言語は gc 中にパフォーマンスの低下に直面することは避けられません。一時ヒープを頻繁に作成するオブジェクト (&abc{}、new、make など) は、ガベージ コレクション中のスキャン時間を短縮します。頻繁に使用する必要がある一時オブジェクトについては、配列キャッシュを通じて直接再利用することを検討してください。 cgo メソッドを使用してメモリ自体を管理し、ガベージ コレクションをバイパスします。この方法は、絶対に必要な場合を除いて推奨されません (予期せぬ問題が発生しやすいため)。もちろん、強制する場合は検討することもできます。このメソッドはまだ非常に明白です~
私たちのサービスの 1 つは、多くの長い接続リクエストを処理する必要があります実装中に、長い接続リクエストごとに読み取りおよび書き込みコルーチンが開かれます、無限の for ループを使用してデータの送受信を継続的に処理します。リモート エンドによって接続が閉じられた場合、これら 2 つのコルーチンが処理されないと、コルーチンは引き続き実行され、占有されているチャネルは解放されません。ここでは十分に注意する必要があり、使用しなくなった後は削除する必要があります。依存チャネルを閉じて、コルーチン内でチャネルが閉じられているかどうかを確認して、確実に終了します。
APR 30TH, 2016 8:02 PM | COMMENTS
このパートでは主に golang gc の入門知識を紹介します。まだまだありますので、少しずつ整理していきます。
主なリファレンスは次のとおりです:
http://morsmachine.dk/machine-gc
は 14これは 2000 年に書かれたもので、当時の gc メカニズムは比較的単純だったと推定されています。新しいバージョンの golang では、gc にさらに大きな変更が加えられるはずです。
go 言語の golang gc に関する関連部分もあります。読書メモ
「メモリ リーク」という用語には馴染みがあるように思えますが、実際にはその正確な意味を見たことがありません。
メモリ リーク は、オペレーティング システムの観点から説明されます。鮮やかな比喩は、「オペレーティング システムがすべてのプロセスに提供できる記憶域 (仮想メモリ空間) が使用されている」ということです。 「ドレイン」の理由は、プログラムの実行中、記憶域スペースが継続的に動的に開かれ、操作の完了後にこれらの記憶域スペースが時間内に解放されないためです。アプリケーションがメモリの特定のセグメントを割り当てた後、設計エラーにより、プログラムがメモリ セグメントの制御を失い、メモリ スペースが無駄になる可能性があります。
プログラムがメモリ空間内のメモリの一部を適用した場合、このメモリ空間はプログラムの実行終了後に解放されず、対応するプログラムには適用された領域の適用を実行する適切な gc メカニズムがありません。リサイクルするとメモリ リークが発生します。
ユーザーの観点から見ると、メモリ リーク自体はユーザーの機能に影響を与えないため害はありませんが、「メモリ リーク」が発生すると
C および C 言語の場合ガベージ コレクションを使用しない場合、主に 2 種類のメモリ リーク、
メモリ リークには関連する問題が数多くありますが、ここでは説明しません。
具体的な利点と欠点については、これを参照してください。ここでは一般的な概要を示します。
そこで、新世代ガベージ コレクションと旧世代ガベージ コレクションという 2 つの異なるガベージ コレクション方法が登場しました (まず分類し、次に適切な薬を処方します)。オブジェクトをそれぞれのスペースに配置し、リサイクルします。新世代のガベージコレクションの速度は旧世代に比べて数桁高速であり、ガベージコレクションの頻度が高くなっても実行効率は旧世代より優れています。これは、ほとんどのオブジェクトのライフ サイクルが非常に短いためであり、古い世代に昇格する必要はまったくありません。
golang の gc は基本的にマークをクリアするという考え方です:
メモリ ヒープ内 (メモリが管理される場合があるため)ヒープ データ構造はページの作成時に使用されるため、ヒープ メモリと呼ばれます) には、他のオブジェクトに関連する可能性のある一連のオブジェクト (これらのオブジェクト間の参照) が格納されます。トレース ガベージ コレクターは、特定の時点で、実行中のプログラムをスキャンすると、ランタイムがすでに認識している既知のオブジェクトのセットがスキャンされます (通常、それらはスタック内に存在するグローバル変数とさまざまなオブジェクトです)。 gc は、これらのオブジェクトをマークし、これらのオブジェクトのステータスを到達可能としてマークし、現在のオブジェクトから到達できる他の場所にあるオブジェクトの参照をすべて検索し、これらのオブジェクトを到達可能なオブジェクトとしてマークします。このステップはマーク フェーズと呼ばれます。このステップの主な目的は、これらのオブジェクトのステータス情報を取得することです。 これらすべてのオブジェクトがスキャンされると、gc はすべての到達不能オブジェクト (到達不能ステータスを持つオブジェクト) を取得し、それらをリサイクルします。このステップはスイープ フェーズと呼ばれ、
クリーニング フェーズと呼ばれます。 gc は、到達可能としてマークされていないオブジェクトのみを収集します。 gc が参照を認識しない場合、まだ使用されているオブジェクトが最終的にリサイクルされ、プログラム実行エラーが発生する可能性があります。
スキャン、リサイクル、クリーニングという 3 つの主要な手順を確認できます。
他の言語に比べて、golang のガベージ コレクション モデルは比較的シンプルだと思います。
gc の問題点
するこの動作は、ガベージ コレクションと呼ばれます。 前の記述によると、gc が参照を適切に識別できるかどうかが gc が正常に動作するための基礎となるため、最初の質問は、gc が参照をどのように識別するかということです。
最大の問題: 参照を識別するのが難しく、マシンコードが何を参照としてカウントするかを認識するのは困難です。参照を誤って逃した場合、解放する準備ができていなかったメモリが誤って解放されてしまうため、戦略は、エラーを少なくするよりも多くすることです。
1 つの戦略は、すべてのメモリ空間を参照 (ポインタ値) の可能性があるものとして扱うことです。これは、保守的なガベージ コレクター (保守的なガベージ コレクター) と呼ばれます。これは、C の Boehm ガベージ コレクターがどのように動作するかです。つまり、メモリ内の通常の変数はポインタとして扱われ、すべてのポインタをカバーしようとします。通常の変数値が指す空間に偶然他のオブジェクトが存在する場合、このオブジェクトはリサイクルされません。 Go 言語の実装はオブジェクトの型情報を完全に認識しており、マークするときにポインタが指すオブジェクトのみをトラバースするため、C 実装でのヒープ メモリの無駄が回避されます (約 10 ~ 30% を解決)。
2014/6 1.3 同時クリーニングの導入(ガベージコレクションとユーザーロジックの同時実行?)
2015/8 1.5 3色マーキング方式の導入
同時クリーニングの導入については、こちらを参照してください。バージョン 1.3 では、go ランタイムはマーク操作とスイープ操作を分離しました。以前と同様に、すべてのタスクの実行が最初に一時停止され、マークが開始されます (マーク部分には引き続き元のプログラムが停止している場合)、中断されたタスクはマーク完了後すぐに再開され、スイープタスクは通常のコルーチンタスクと同様に他のタスクと並行して実行されます。マルチコア プロセッサで実行されている場合、go はビジネス コードの実行に影響を与えることなく、別のコアで gc タスクを実行しようとします。go チーム自体は、一時停止時間が 50% ~ 70% 削減されると言っています。
基本的なアルゴリズムは前述のクリーニングとリサイクルですが、Golang gc 最適化の核心は STW (Stop The World) 時間をどんどん短くすることです。
これまでにたくさん述べましたが、GC のスター効率を測定し、それがプログラムの実行に影響を与えるかどうかを判断するにはどうすればよいでしょうか?最初の方法は、godebug の環境変数を設定することです。詳細については、この記事を参照してください。これは非常に良い記事です: リンク。たとえば、 GODEBUG=gctrace=1 ./myserver
を実行します。出力結果を理解するには、gc の原理をさらに詳細に分析する必要があります。この記事の利点は、golang の gc 時間を決定する要因が明確に示されているため、別のターゲットを取得することもできることです。 gc 時間を改善する方法:
前回の分析によると、golang の gc はクリアマーク方式を使用していることもわかり、gc の合計時間は次のようになります:
Tgc = Tseq Tmark Tweak
(T は時間を表します)
Tmark に関連: 1 ガベージ コレクション プロセス中に、ヒープ内のアクティブ オブジェクトの数、2 ポインターを持つアクティブ オブジェクトによって占有されるメモリの合計量、3 アクティブ オブジェクト内のポインターの数。
たとえば、現在のプログラムは 4M のメモリ (ここでは ヒープ メモリ ) を使用しており、これは、プログラムが現在到達可能なメモリが 4M であることを意味します。到達可能* (1 GOGC/100)=8M の場合、gc がトリガーされ、関連する gc 操作が開始されます。
GOGC のパラメータをどのように設定するかは、GC の頻度を減らすために GOGC パラメータを増やすなど、本番環境の実際のシナリオに応じて決定する必要があります。 ヒント
詳細な洞察を得るには、gdb を使用することが不可欠です。この記事では、gdb の使用に関するいくつかの入門ヒントをまとめました。
オブジェクト割り当ての削減いわゆるオブジェクト割り当ての削減とは、実際にはオブジェクトの再利用を試みることです。たとえば、次の 2 つの関数定義:
最初の関数には仮パラメータがなく、呼び出されるたびに [] バイトが返されます。2 番目の関数には、呼び出されるたびに buf の仮パラメータがあります。[ ]byte 型オブジェクトの場合は、読み取られたバイト数を返します。 最初の関数は呼び出されるたびにスペースを割り当てるため、gc にさらなる負荷がかかります。 2 番目の関数は、呼び出されるたびに仮パラメータ宣言を再利用します。
文字列と [] バイトの変換に関する決まり文句文字列と [] バイト間の変換は gc に負担をかけます。gdb を通じて、まず 2 つのデータ構造を比較できます。
両者を変換すると、基になるデータ構造がコピーされるため、gc 効率が低下します。解決策の 1 つの方法は、特にデータ送信では常に []byte を使用することです。[]byte には、文字列で一般的に使用される効果的な演算も数多く含まれています。もう 1 つは、コピーを避けるために、下位レベルの操作を使用して直接変換することです。主に unsafe.Pointer を直接変換に使用する WeChat「Yuhen Academy」のパフォーマンス最適化の前半部分を参照できます。
unsafe の使い方については別記事にまとめられそうな気がするので、まずはここに関連情報を列挙してみます http://studygolang.com/articles/685 直感的に unsafe.Pointer Into void が理解できると思います※C、golangでは各種ポインタを変換するブリッジに相当します。
uintptr の基礎となる型は int で、ポインタが指すアドレスの値を保持できます。 unsafe.Pointer との間で変換できます。主な違いは、uintptr はポインター操作に参加できるのに対し、unsafe.Pointer はポインター変換のみを実行でき、ポインター操作は実行できないことです。ポインタ演算に golang を使用したい場合は、これを参照してください。特定のポインター操作を実行する場合、オフセットなどのさらなる計算を行う前に、まず uintptr 型に変換する必要があります。
文字列の接続には控えめに使用する 文字列の接続に使用すると新しいオブジェクトが生成され、gc の効率が低下するため、append 関数を使用するのが最善の方法です。
しかし、別の欠点もあります。たとえば、次のコードを参照してください。
追加操作を使用すると、配列のスペースが 1024 から 1312 に増加します。幸いなことに、最初に領域を割り当てるときに領域計画が行われるため、コード管理コストが増加し、GC への負荷が軽減され、コード効率が向上します。
以上がGolang GC ガベージコレクションの仕組みの詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。