Go はなぜこれほど多くの仮想メモリを占有するのでしょうか?

Go はなぜこれほど多くの仮想メモリを占有するのでしょうか?

[[349727]]

この記事は、陳建宇氏が執筆したWeChatパブリックアカウント「私の脳は揚げ魚です」から転載したものです。この記事を転載する場合は、Nao Nao Jin Jian Yu Leの公開アカウントまでご連絡ください。

以前、クラスメイトから、あるサービスのコンテナがメモリ制限を超えたために再起動を続けているという話がありました。彼は、メモリ リークがあるかどうかを尋ね、問題を回避するためにそれを調べて解決するように依頼しました。

私たちはショックを受け、すぐに監視+警報システムとパフォーマンス分析をチェックしたところ、アプリケーション指標はまったく高くなく、漏れがあるようには見えませんでした。

いったい何が問題なのでしょうか?コンテナに入り、上位のシステムインジケーターをチェックしました。

  1. PID VSZ RSS ... コマンド
  2. 67459 2007m 136m ... ./eddycjy-server

オーバーヘッドはそれほどないように見えますが、Go プロセスは 1 つだけですか?それでおしまい?

よく見てみると、クラスメイトがVSZがすごく高いと言っていて、あるクラウドのコンテナメモリインジケーターがVSZ値に意外に近かったそうです。そのため、VSZ が原因であると疑い、一定の相関関係があると感じました。

この推測は正しいでしょうか?

基礎

この記事では、主に Go プロセスの VSZ を分析して、なぜそれが「高い」のかを確認します。

最初のセクションは、前提となる補足知識であり、順番に読むことができます。

VSZとは

VSZ は、プロセスが使用できる仮想メモリの合計サイズです。これには、スワップアウトされたメモリ (スワップ)、割り当てられたが未使用のメモリ、共有ライブラリからのメモリなど、プロセスがアクセスできるすべてのメモリが含まれます。

なぜ仮想メモリなのでしょうか?

先ほど、VSZ は実際にはプロセスの仮想メモリの合計サイズであることを学びました。 VSZ を理解するには、まず「なぜ仮想メモリが必要なのか」を理解する必要があります。

基本的に、システム内のプロセスは CPU とメイン メモリのリソースを他のプロセスと共有します。

したがって、現代のオペレーティング システムでは、複数のプロセスの使用が非常に一般的です。プロセスが多すぎて大量のメモリを必要とする場合、仮想メモリがないと物理メモリが不足し、一部のタスクが実行に失敗したり、非常に奇妙な現象が発生したりする可能性があります。

たとえば、あるプロセスが別のプロセスが使用しているメモリに誤って書き込むと、メモリが破損してしまうため、仮想メモリは非常に重要な媒体となります。

仮想メモリには何が含まれていますか?

仮想メモリは次のように分割されます。

  • カーネル仮想メモリ。
  • 仮想メモリを処理します。

各プロセスの仮想メモリは独立しており、内部構造は下図のようになります。

カーネル仮想メモリにはカーネル コードとデータ構造が含まれます。

カーネル仮想メモリ内の特定の領域は、すべてのプロセスで共有される物理ページにマップされるため、「カーネル仮想メモリ」には実際には「物理メモリ」が含まれており、両者の間にはマッピング関係があることがわかります。

アプリケーション シナリオでは、各プロセスはカーネル コードとグローバル データ構造も共有するため、すべてのプロセスの物理ページにマップされます。

仮想メモリの重要な機能

メモリをより効率的に管理し、エラーを減らすために、現代のシステムでは、今日の主役である仮想メモリ (VM) と呼ばれるメインメモリの抽象的な概念が提供されています。

仮想メモリは、ハードウェア例外、ハードウェア アドレス変換、メイン メモリ、ディスク ファイル、およびカーネル ソフトウェアが相互作用する場所です。各プロセスに大規模で一貫性のあるプライベートなアドレス空間を提供します。仮想メモリは、次の 3 つの重要な機能を提供します。

メイン メモリをディスク上に格納されたアドレス空間のキャッシュとして扱い、メイン メモリ内にアクティブな領域のみを保持し、必要に応じてディスクとメイン メモリ間でデータを転送します。このようにして、メインメモリを効率的に使用します。

各プロセスに一貫したアドレス空間を提供するため、メモリ管理が簡素化されます。

各プロセスのアドレス空間が他のプロセスによって破損されるのを防ぎます。

まとめ

上記とはかなり相違があるかもしれません。簡単に言えば、この記事では、次のような知識ポイントに焦点を当てます。

  • 仮想メモリは、あらゆる種類のメモリが相互作用する場所です。そこには「それ自体」以上のものが含まれています。この記事では、コード、データ、ヒープ、スタック セグメント、共有ライブラリが含まれるプロセス仮想メモリである VSZ にのみ焦点を当てます。
  • メモリ保護ツールとして、仮想メモリはプロセス間のメモリ空間が独立しており、他のプロセスの影響を受けないことを保証します。したがって、各プロセスの VSZ サイズは異なり、互いに影響しません。
  • 仮想メモリが存在するため、システムによって各プロセスに割り当てられるメモリの合計は、実際に使用可能な物理メモリよりも大きくなる可能性があります。したがって、プロセスの物理メモリは常に仮想メモリよりもはるかに少ないことがわかります。

トラブルシューティング

基礎を理解した後、私たちは正式に問題のトラブルシューティングを開始しました。最初のステップは、ビジネス ロジックのない Go プログラムの初期 VSZ がどのようになるかを確認するためのテスト プログラムを作成することです。

テスト

アプリケーションコード:

  1. 関数main() {
  2. r := gin.Default ()です。
  3. r.GET( "/ping" , func(c *gin.Context) {
  4. c.JSON(200, gin.H{
  5. 「メッセージ」 : 「ポン」
  6. })
  7. })
  8. r.Run( ":8001" )
  9. }

進行状況を確認します:

  1. $ ps 67459 追加
  2. ユーザーPID %CPU %MEM VSZ RSS ...
  3. eddycjy 67459 0.0 0.0 4297048 960 ...

結果から判断すると、VSZは4297048K、つまり約4Gです。一見するとかなり怖いです。明らかにビジネスロジックはありませんが、なぜこんなに高いのでしょうか?本当に不思議ですね。

漏れがないか確認する

不明な場合は、まずruntime.MemStatsとpprofを調べて、アプリケーションにリークがあるかどうかを判断します。ただし、これはビジネス ロジックのないデモ プログラムであるため、アプリケーションとは直接関係がないことは確かです。

  1. # ランタイム.MemStats
  2. # 割り当て = 1298568
  3. # 合計割り当て = 1298568
  4. # システム = 71893240
  5. # ルックアップ = 0
  6. # malloc = 10013
  7. 解放数 = 834
  8. # ヒープ割り当て = 1298568
  9. #ヒープシステム = 66551808
  10. #ヒープアイドル = 64012288
  11. #ヒープ未使用数 = 2539520
  12. #ヒープ解放数 = 64012288
  13. # ヒープオブジェクト = 9179
  14. ...

FAQへ

それから、私の最初の反応は Go FAQ を調べることでした (以前に見たことがあり、覚えていたため)。質問は「なぜ Go プロセスはこれほど多くの仮想メモリを使用するのですか?」で、答えは次のとおりです。

Go メモリ アロケータは、割り当ての領域として仮想メモリの大きな領域を予約します。この仮想メモリは特定の Go プロセスに対してローカルです。予約によって他のプロセスのメモリが奪われることはありません。

Go プロセスに割り当てられた実際のメモリ量を確認するには、Unix top コマンドを使用し、RES (Linux) または RSIZE (macOS) 列を参照します。

この FAQ は 2012 年 10 月に提出されました。それ以降、何年にもわたってそれ以上の説明はありません。問題とフォーラムを調べたところ、解決済みの問題の一部はすべて FAQ を指していることがわかりました。これでは明らかに私の好奇心を満たすことができなかったので、私は中に何が入っているのか調べ続けました。

メモリマップを表示する

上の図では、プロセス仮想メモリには主にコード、データ、ヒープ、スタック セグメント、共有ライブラリが含まれることを説明しました。当初は、プロセスが何らかのメモリ マッピングを実行し、その結果、大量のメモリ領域が予約されたことが疑われます。これを確認するには、次のコマンドを使用してチェックします。

  1. $ vmmap --wide 67459  
  2. ...
  3. ====プロセス 67459書き込み不可領域
  4. 領域タイプ開始 -終了[VSIZE RSDNT ダーティ スワップ] PRT/最大SHRMOD パージ領域詳細
  5. __TEXT 00000001065ff000-000000010667b000 [496K 492K 0K 0K] rx/rwx SM=COW /bin/zsh
  6. __LINKEDIT 0000000106687000-0000000106699000 [72K 44K 0K 0K] r --/rwx SM=COW /bin/zsh  
  7. MALLOC メタデータ 000000010669b000-000000010669c000 [4K 4K 4K 0K] r --/rwx SM=COW DefaultMallocZone_0x10669b000 ゾーン構造 
  8. ...
  9. __TEXT 00007fff76c31000-00007fff76c5f000 [184K 168K 0K 0K] rx/rx SM=COW /usr/lib/system/libxpc.dylib
  10. __LINKEDIT 00007fffe7232000-00007ffff32cb000 [192.6M 17.4M 0K 0K] r --/r-- SM=COW dyld 共有キャッシュ結合 __LINKEDIT  
  11. ...
  12.  
  13. ====プロセス 67459書き込み可能領域
  14. 領域タイプ開始 -終了[VSIZE RSDNT ダーティ スワップ] PRT/最大SHRMOD パージ領域詳細
  15. __DATA 000000010667b000-0000000106682000 [28K 28K 28K 0K] rw-/rwx SM=COW /bin/zsh
  16. ...
  17. __DATA 0000000106716000-000000010671e000 [32K 28K 28K 4K] rw-/rwx SM=COW /usr/lib/zsh/5.3/zsh/zle.so
  18. __DATA 000000010671e000-000000010671f000 [4K 4K 4K 0K] rw-/rwx SM=COW /usr/lib/zsh/5.3/zsh/zle.so
  19. __DATA 0000000106745000-0000000106747000 [8K 8K 8K 0K] rw-/rwx SM=COW /usr/lib/zsh/5.3/zsh/complete.so
  20. __データ 000000010675a000-000000010675b000 [4K 4K 4K 0K] rw-
  21. ...

この部分では、主にmacOSのvmmapコマンドを使用してメモリマッピング状況を表示し、このプロセスのメモリマッピング状況を知ることができます。出力分析から、これらの関連する共有ライブラリが占めるスペースは大きくないことがわかります。 VSZ が高くなる根本的な原因は、共有ライブラリやバイナリ ファイルには存在しません。しかし、問題点である大量のメモリ空間を確保する動作は見られませんでした。

注: Linux システムの場合は、cat /proc/PID/maps または cat /proc/PID/smaps を使用して表示できます。

システムコールを表示する

メモリ マップにメモリ領域を予約する明確な動作が見られないため、次のようにプロセスのシステム コールを調べて、メモリ操作の動作があるかどうかを確認します。

  1. $ sudo dtruss -a ./awesomeProject
  2. ...
  3. 4374/0x206a2: 15620 6 3 mprotect(0x1BC4000, 0x1000, 0x0) = 0 0
  4. ...
  5. 4374/0x206a2: 15781 9 4 sysctl([CTL_HW, 3, 0, 0, 0, 0] (2), 0x7FFEEFBFFA64, 0x7FFEEFBFFA68, 0x0, 0x0) = 0 0
  6. 4374/0x206a2: 15783 3 1 sysctl([CTL_HW, 7, 0, 0, 0, 0] (2), 0x7FFEEFBFFA64, 0x7FFEEFBFFA68, 0x0, 0x0) = 0 0
  7. 4374/0x206a2: 15899 7 2 mmap(0x0, 0x40000, 0x3, 0x1002, 0xFFFFFFFFFFFFFFFF, 0x0) = 0x4000000 0
  8. 4374/0x206a2: 15930 3 1 mmap(0xC000000000, 0x4000000, 0x0, 0x1002, 0xFFFFFFFFFFFFFFFF, 0x0) = 0xC000000000 0
  9. 4374/0x206a2: 15934 4 2 mmap(0xC000000000, 0x4000000, 0x3, 0x1012, 0xFFFFFFFFFFFFFFFF, 0x0) = 0xC000000000 0
  10. 4374/0x206a2: 15936 2 0 mmap(0x0, 0x2000000, 0x3, 0x1002, 0xFFFFFFFFFFFFFFFF, 0x0) = 0x59B7000 0
  11. 4374/0x206a2: 15942 2 0 mmap(0x0, 0x210800, 0x3, 0x1002, 0xFFFFFFFFFFFFFFFF, 0x0) = 0x4040000 0
  12. 4374/0x206a2: 15947 2 0 mmap(0x0, 0x10000, 0x3, 0x1002, 0xFFFFFFFFFFFFFFFF, 0x0) = 0x1BD0000 0
  13. 4374/0x206a2: 15993 3 0 マッドアドバイズ(0xC000000000, 0x2000, 0x8) = 0 0
  14. 4374/0x206a2: 16004 2 0 mmap(0x0, 0x10000, 0x3, 0x1002, 0xFFFFFFFFFFFFFFFF, 0x0) = 0x1BE0000 0
  15. ...

このセクションでは、macOS の dtruss コマンドを使用して、このプログラムの実行時に行われたすべてのシステム コールを監視および表示し、メモリ管理に関連するメソッドが次のとおりであることを発見しました。

  • mmap: 新しい仮想メモリ領域を作成しますが、システムが mmap を呼び出すと、仮想メモリ内のスペースにのみ適用され、実際の物理メモリの割り当てとマップは行われないことに注意してください。この領域にアクセスすると、実際に現時点での物理メモリが割り当てられます。そして、実際のアプリケーション プロセスでは、VSZ が増加してもメモリ領域が正式に使用されない場合は、物理メモリは増加しません。
  • madvise: MADV_NORMAL、MADV_RANDOM、MADV_SEQUENTIAL、MADV_WILLNEED、MADV_DONTNEED など、メモリ使用量に関する提案を提供します。
  • mprotect: メモリ領域の保護ステータスを設定します。例: PROT_NONE、PROT_READ、PROT_WRITE、PROT_EXEC、PROT_SEM、PROT_SAO、PROT_GROWSUP、PROT_GROWSDOWN など。
  • sysctl: カーネルの実行中にカーネルの動作パラメータを動的に変更します。

ここでさらに疑わしいのは、dtruss の最終統計で 10 回以上呼び出される mmap メソッドです。 Go ランタイムでは多くの仮想メモリ要求が行われると考えられます。

読み進めて、どの段階で仮想メモリ領域が要求されるかを確認しましょう。

注: Linux を使用している場合は、strace コマンドを使用できます。

Go ランタイムを表示

プロセスを開始する

以上の分析により、Go プログラムの起動時に VSZ が低くなっていないことがわかり、共有ライブラリなどが原因ではなく、システムコールがプログラム起動時に mmap などのメソッドを呼び出していることが分かります。

すると、Go が初期化フェーズ中にこのメモリ領域を予約していると完全に推測できます。最初に行う必要があるのは、Go ブート プロセスをチェックして、それがどこに適用されているかを確認することです。

起動プロセスは次のとおりです。

  1. グラフTD
  2. A(rt0_darwin_amd64.s:8<br/>_rt0_amd64_darwin) -->|JMP| B(asm_amd64.s:15<br/>_rt0_amd64)  
  3. B --> |JMP|C(asm_amd64.s:87<br/>runtime-rt0_go)  
  4. C --> D(runtime1.go:60<br/>ランタイム引数)  
  5. D --> E(os_darwin.go:50<br/>ランタイム-osinit)  
  6. E --> F(proc.go:472<br/>ランタイム-schedinit)  
  7. F --> G(proc.go:3236<br/>ランタイム-newproc)  
  8. G --> H(proc.go:1170<br/>runtime-mstart)  
  9. H --> I (新しく作成された p と m でランタイムメインを実行する)  
  • runtime-osinit: CPU コアの数を取得します。
  • runtime-schedinit: プログラム実行環境 (スタック、メモリアロケータ、ガベージコレクション、P などを含む) を初期化します。
  • runtime-newproc: 新しい G を作成し、runtime.main をバインドします。
  • runtime-mstart: スレッド M を開始します。

注: @曹大の「Go プログラムの起動プロセス」と @全成の「Go プログラムの実行方法」を読むことをお勧めします。

動作環境を初期化する

明らかに、次のように実行時に schedinit メソッドを調べたいと思います。

  1. 関数schedinit() {
  2. ...
  3. スタック初期化()
  4. mallocinit()
  5. mcommoninit(_g_.m)
  6. cpuinit() // alginit の前に実行する必要があります
  7. alginit() //この呼び出しの前にマップを使用してはいけませ
  8. modulesinit() // activeModulesを提供する
  9. typelinksinit() // マップ、activeModules を使用する
  10. itabsinit() // activeModules を使用する
  11.  
  12. msigsave(_g_.m)
  13. initSigmask = _g_.m.sigmask
  14.  
  15. ゴーグス()
  16. ゴーエンブス()
  17. パースデバッグ変数()
  18. gcinit()
  19. ...
  20. }

使用の観点から見ると、mallocinit メソッドがメモリ アロケータを初期化することは明らかです。引き続き下を見ていきましょう。

メモリアロケータを初期化する

マロシニット

次に、mallocinit メソッドを正式に分析します。ブート プロセスでは、mallocinit が主に Go プログラムのメモリ アロケータの初期化を担当します。今日は、主に仮想メモリアドレスを次のように分析します。

  1. mallocinit()関数{
  2. ...
  3. sys.PtrSize == 8の場合{
  4. i := 0x7fの場合; i >= 0;私- {  
  5. var p uintptr
  6. スイッチ{
  7. GOARCH == "arm64" && GOOS == "darwin"の場合:
  8. p = uintptr(i)<<40 | uintptrマスク&(0x0013<<28)
  9. GOARCH == "arm64" の場合:
  10. p = uintptr(i)<<40 | uintptrマスク&(0x0040<<32)
  11. GOOS == "aix" の場合:
  12. i == 0 の場合 {
  13. 続く 
  14. }
  15. p = uintptr(i)<<40 | uintptrマスク&(0xa0<<52)
  16. レースが有効の場合:
  17. ...
  18. デフォルト
  19. p = uintptr(i)<<40 | uintptrマスク&(0x00c0<<32)
  20. }
  21. ヒント:= (*arenaHint)(mheap_.arenaHintAlloc.alloc())
  22. ヒント.addr = p
  23. hint.next 、mheap_.arenaHints = mheap_.arenaHints、ヒント
  24. }
  25. }それ以外{
  26. ...
  27. }
  28. }
  • 現在のシステムが 64 ビットか 32 ビットかを判断します。
  • 0x7fc000000000~0x1c000000000 の予約アドレスを設定します。
  • 現在の GOARCH、GOOS、または競合チェックが有効になっているかどうかを判断して、さまざまな状況に応じて異なるサイズの連続メモリ アドレスを適用します。ここで、p は適用する連続メモリ アドレスの開始アドレスです。
  • 計算したアリーナの情報をarenaHintに保存します。

なぜ 32 ビット システムか 64 ビット システムかを判断する必要があるのか​​と尋ねる友人もいるかもしれません。これは、異なるビット番号の仮想メモリのアドレス指定範囲が異なるため、それらを区別する必要があり、そうしないと、高ビットの仮想メモリ マッピングの問題が発生するためです。予約スペースを申請する際には、arenaHints リスト内のノードであり、次の構造を持つ arenaHint 構造について言及することがよくあります。

  1. arenaHint 構造体型 {
  2. アドレス uintptr
  3. ダウンブール
  4. 次へ*arenaHint
  5. }
  • addr: アリーナの開始アドレス
  • down: 最後のアリーナですか?
  • next: 次のアリーナのポインタアドレスヒント

では、ここで言及されているアリーナとは何でしょうか?これは実際には Go のメモリ管理の概念です。 Go ランタイムは、要求された仮想メモリを次のように 3 つの大きなブロックに分割します。

画像

  • spans: アリーナ領域のページ番号と mspan 間のマッピング関係を記録します。
  • ビットマップ: アリーナの使用状況を識別します。機能的には、アリーナのどのスペース アドレスにオブジェクトが格納されているかを識別するために使用されます。
  • arean: arean は実際には mheap によって管理される Go のヒープ領域であり、その MaxMem は 512GB-1 です。機能面では、Go は初期化中に領域に連続した仮想メモリ空​​間アドレスを適用し、そこに保持します。ヒープ上のスペースが本当に必要な場合は、処理のために領域から取り出され、物理メモリに変換されます。

ここでは、Go メモリ内の area 領域の役割を理解する必要があります。

mmap

上記の分析により、mallocinit の目的はすでにわかっていますが、前に見た mmap システム コールが mallocinit とどのような関係があり、どのように関連しているかについてはまだ混乱しているかもしれません。次に、次のような下位レベルのコードを見てみましょう。

  1. func sysAlloc(n uintptr, sysStat *uint64) 安全でないポインタ {
  2. p, err := mmap(nil, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
  3. ...
  4. mSysStatInc(sysStat, n)
  5. 戻るp
  6. }
  7.  
  8. func sysReserve(v unsafe.Pointer, n uintptr) unsafe.Pointer {
  9. p, エラー := mmap(v, n, _PROT_NONE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
  10. ...
  11. }
  12.  
  13. func sysMap(v unsafe.Pointer, n uintptr, sysStat *uint64) {
  14. ...
  15. munmap(v, n)
  16. p, エラー := mmap(v, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_FIXED|_MAP_PRIVATE, -1, 0)
  17. ...
  18. }

Go ランタイムには、一連のシステムレベルのメモリ呼び出しメソッドがあります。この記事で取り上げる主なものは次のとおりです。

  • sysAlloc: OS システムからクリアされたメモリ領域を適用します。呼び出しパラメータは _PROT_READ|_PROT_WRITE、_MAP_ANON|_MAP_PRIVATE です。結果はメモリに揃える必要があります。
  • sysReserve: OS システムからメモリ アドレス空間を予約します。現時点では物理メモリは割り当てられていません。呼び出しパラメータは _PROT_NONE、_MAP_ANON|_MAP_PRIVATE であり、結果はメモリに揃える必要があります。
  • sysMap: 予約されたメモリ領域を使用する必要があることを OS に通知します。呼び出しパラメータは、_PROT_READ|_PROT_WRITE、_MAP_ANON|_MAP_FIXED|_MAP_PRIVATE です。

理にかなっているように思えますが、mallocinit メソッドが初期化されるとき、mmap メソッドはどこで関与するのでしょうか?以下のように、表面からは明らかではありません。

  1. i := 0x7fの場合; i >= 0;私- {  
  2. ...
  3. ヒント:= (*arenaHint)(mheap_.arenaHintAlloc.alloc())
  4. ヒント.addr = p
  5. hint.next 、mheap_.arenaHints = mheap_.arenaHints、ヒント
  6. }

実際、mheap_.arenaHintAlloc.alloc() を呼び出すと、mheap の下の sysAlloc メソッドが呼び出され、sysAlloc は mmap メソッドとの呼び出し関係を持ちますが、このメソッドは次のように通常の sysAlloc とはまったく異なります。

  1. var mheap_ mheap
  2. ...
  3. func (h *mheap) sysAlloc(n uintptr) (v unsafe.Pointer,サイズuintptr) {
  4. ...
  5. h.arenaHints != nil {の場合
  6. ヒント := h.arenaHints
  7. p := ヒント.addr
  8. ヒント.down {
  9. p -= n
  10. }
  11. p+n < pの場合{
  12. v = ゼロ
  13. }それ以外の場合、arenaIndex(p+n-1) >= 1<<arenaBits {
  14. v = ゼロ
  15. }それ以外{
  16. v = sysReserve(unsafe.Pointer(p), n)
  17. }
  18. ...
  19. }

mheap.sysAlloc が実際に sysReserve メソッドを呼び出していることに驚かれるかもしれません。sysReserve メソッドは、OS システムからメモリ アドレス空間を予約するための特定のメソッドです。驚きじゃないですか?すべてがつながっているようです。

まとめ

このセクションでは、まずテスト プログラムを作成し、次に型破りなトラブルシューティングのアイデアに基づいて、疑わしい点を段階的に追跡しました。全体的なプロセスは次のとおりです。

  • top や ps などのコマンドを使用して、プロセスの実行ステータスを表示し、基本的な指標を分析します。
  • pprof やruntime.MemStats などのツール チェーンを使用して、アプリケーションの実行状態を表示し、リークがあるかどうか、またはアプリケーション レベルでデータが多くなっている場所を分析します。
  • vmmap コマンドを使用して、プロセスのメモリ マッピングを表示し、共有ライブラリなど、プロセス仮想空間内の特定の領域が比較的高いかどうかを分析します。
  • dtruss コマンドを使用して、プログラムのシステム コールを表示し、発生する可能性のある特殊な動作を分析します。たとえば、分析では、mmap メソッド呼び出しの割合が比較的高いことがわかったので、Go が起動時に大量のメモリ領域を予約していると疑う十分な理由があります。
  • 上記の分析により、どの段階でこれほど多くのメモリ領域が要求されるかを判断し、ソースコードの前に秘密がないため推測する必要がないため、Go ランタイムでさらにソースコード分析を行うことができます。

結論から言うと、VSZ(プロセス仮想メモリサイズ)は共有ライブラリなどとはほとんど関係がありません。主にGoランタイムに直接関係しており、前の図に示したランタイムヒープ(malloc)に関係しています。 Go ランタイムに変換すると、メモリ アロケータ mallocinit の初期化フェーズ中に一定量の仮想空間が予約されます。

仮想メモリ空​​間の予約に何が影響するかは哲学的な問題です。ソースコードの観点から見ると、主なポイントは次のとおりです。

  • 異なる OS システム アーキテクチャ (GOARCH/GOOS) とビット サイズ (32/64 ビット) の影響を受けます。
  • メモリ アライメントの影響を受けるため、計算されたメモリ スペース サイズは、保持する前にアライメントする必要があります。

要約する

Go がどこにあるのか、どのような要因がそれに影響を与えるのか、そして大量の仮想メモリ空​​間を予約するためにどのようなメソッドが呼び出されるのかを段階的に分析して説明しました。ただし、プロセス仮想メモリ (VSZ) の高さについては間違いなく心配することになります。何か問題はありますか?私の分析は次のとおりです。

  • VSZ は、実際にそれほど多くの物理メモリを使用することを意味するものではないため、心配する必要はありません。
  • VSZ は GC に圧力をかけません。 GC はプロセスによって実際に使用される物理メモリを管理し、VSZ は実際に使用されるまでそれほどコストがかかりません。
  • VSZ は基本的にアクセス不可能なメモリ マッピングであり、メモリへのアクセス権がない (読み取り、書き込み、実行は許可されない) ことを意味します。

考える

Go VSZ が高くても大きな問題は発生しないので、ここでは一安心です。しかし、よく考えてみると、なぜ Go はこれほど多くの仮想メモリを必要とするのでしょうか?

全体的な考慮事項は次のとおりです。

  • Go は、アリーナとビットマップのその後の使用を考慮して設計されており、メモリ アドレス空間全体を事前に予約します。
  • Go ランタイムとアプリケーションが徐々に使用されるようになると、実際にメモリを適用して使用するようになります。このとき、アリーナとビットマップのメモリ アロケータは、事前に予約されたメモリ アドレス空間を実際に使用可能な物理メモリに変更するだけで済み、パフォーマンスが大幅に向上します。

参照する

Golang による仮想メモリの割り当てが高い

GOメモリ管理

GoBigVirtualSize

Goプログラムメモリ使用

曹達の囲碁プログラムの起動プロセス

Quan Cheng の Go プログラムはどのように実行されますか?

Ou Shenのボンネットの下

<<:  クラウドで必要な 5 つの機械学習スキル

>>:  5G をサポートするにはクラウドネイティブ エッジが本当に必要ですか?

推薦する

新しい視点: ウェブサイトのキーワードランキングの変化を必ずしも外部要因に帰するべきではない

友人のウェブサイトのキーワードランキングが変わっているのをよく見かけるので、その理由を探して、検索エ...

先週末の百度大幅アップデート後のウェブサイト外部リンク減少の理由分析

今朝、いつものように管理しているいくつかのウェブサイトを開いたところ、これらのウェブサイトのランキン...

病院でブランドサービスマーケティングを成功させる方法

現在、どの都市にもさまざまな規模の病院があり、ユーザーには多くの選択肢があります。この点だけから見て...

事例分析:中国国内の検索エンジンの類似点と相違点を探る

オンラインプロモーションは多くのレベルに分かれており、ウェブサイト最適化ランキングは最も一般的なウェ...

papashosting-$2.99/1g メモリ/30g SSD/1T トラフィック/ペンシルバニア

papashosting.com は新しく設立された会社のようです。ドメイン名登録、仮想ホスティング...

SEOの初体験:成功するには、まず自分の性格を磨かなければならない

偶然、SEO業界に加わりました。しばらく勉強した後、SEO についてある程度理解するようになりました...

タオバオの1元フラッシュセールは個人のプライバシーを漏らす恐れがある:規制は難しく、違反のコストは低い

[はじめに] ユーザーのプライバシーを保護するために、直接消費者データを保有する Taobao など...

電子商取引ウェブサイトが単一ページを通じてどのようにマーケティングを行うことができるかについての簡単な分析

長い間、マーケティングに関する記事を書いていませんでした。今日は、eコマースサイトのシングルページマ...

ホームページの最適化、アクションが重要です!

社内でホームページについて、またそれを改善するための方法について議論し続けるなら、過去に思い描いてい...

タイトルを変更してウェブサイトの権威を失うことを恐れないでください

月収10万元の起業の夢を実現するミニプログラム起業支援プラン多くの人が、ウェブサイトのタイトル変更に...

第5回の支払いライセンスには国内ライセンスが1つしかなく、今後発行されない可能性がある。

第4回決済ライセンス発行から1か月余り後、中国人民銀行は先日、非金融機関向けに第5回「決済業務ライセ...

私たちは本当に SEO を理解しているのでしょうか?

SEO 最適化に関しては、業界のベテランであっても、この分野に参入したばかりの新人であっても、多かれ...

分散環境で DNS サービスの攻撃対象領域を拡大する方法

私は現在、RCNTEC株式会社に勤務しており、日々分散環境に取り組んでいます。 ISC BIND を...

新規サイトが常に1桁で含まれる理由のまとめ

一昨日、A5タスクエリアでタスクを見ました。ウェブサイトのインクルードが常に10未満で、何をしても無...