Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 19690] ファイルの概要

このコミットは、Go言語の syscall パッケージ内の netlink_linux.go ファイルにある NetlinkRIB 関数におけるメモリ割り当ての最適化に関するものです。具体的には、ループ内で繰り返し行われていたバイトスライスの割り当てと Getpagesize() の呼び出しを削減し、メモリ使用量とパフォーマンスを改善します。

コミット

commit 8aea9a00a838736189d4a9e381f916b0c3ceaf1d
Author: Cristian Staretu <unclejacksons@gmail.com>
Date:   Wed Jul 9 18:50:38 2014 +1000

    syscall: NetlinkRIB, avoid allocation in loop
    
    NetlinkRIB is currently allocating a page sized slice of bytes in a
    for loop and it's also calling Getpagesize() in the same for loop.
    
    This CL changes NetlinkRIB to preallocate the page sized slice of
    bytes before reaching the for loop. This reduces memory allocations
    and lowers the number of calls to Getpagesize() to 1 per NetlinkRIB
    call.
    
    This CL reduces the allocated memory from 141.5 MB down to 52 MB in
    a test.
    
    LGTM=crawshaw, dave
    R=dave, dsymonds, crawshaw
    CC=bradfitz, dsymonds, golang-codereviews
    https://golang.org/cl/110920043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/8aea9a00a838736189d4a9e381f916b0c3ceaf1d

元コミット内容

NetlinkRIB 関数が、for ループ内でページサイズのバイトスライスを繰り返し割り当てており、同じループ内で Getpagesize() を呼び出している問題を修正します。この変更により、NetlinkRIBfor ループに入る前にページサイズのバイトスライスを事前に割り当てるようになります。これにより、メモリ割り当てが削減され、Getpagesize() の呼び出し回数が NetlinkRIB の呼び出しごとに1回に減少します。この変更は、テストにおいて割り当てられるメモリを141.5 MBから52 MBに削減しました。

変更の背景

src/pkg/syscall/netlink_linux.go 内の NetlinkRIB 関数は、LinuxカーネルとのNetlink通信において、ルーティング情報ベース (RIB) のデータを取得するために使用されます。元の実装では、この関数がデータを読み取る for ループの各イテレーションで、Getpagesize() を呼び出してシステムページサイズを取得し、そのサイズの新しいバイトスライスを make([]byte, Getpagesize()) で割り当てていました。

このアプローチには以下の問題がありました。

  1. 頻繁なメモリ割り当て: ループの各イテレーションで新しいバイトスライスがヒープに割り当てられるため、ガベージコレクション (GC) の負荷が増大し、アプリケーションのパフォーマンスに悪影響を与える可能性がありました。特に、大量のNetlinkデータを処理する場合、このオーバーヘッドは顕著になります。
  2. Getpagesize() の繰り返し呼び出し: Getpagesize() はシステムコールであり、その結果は通常、システムの起動中に一度決定され、実行中に変化することはありません。にもかかわらず、ループ内で繰り返し呼び出されることは無駄な処理であり、わずかながらもCPUサイクルを消費していました。

このコミットは、これらの非効率性を解消し、NetlinkRIB 関数のメモリ使用量を削減し、実行効率を向上させることを目的としています。コミットメッセージに記載されているように、テスト環境で141.5 MBから52 MBへの大幅なメモリ削減が確認されており、その効果は明らかです。

前提知識の解説

Netlinkは、Linuxカーネルとユーザー空間プロセス間の通信メカニズムです。これは、ネットワーク関連の情報を取得したり設定したりするために広く使用されます。例えば、ルーティングテーブルの操作、ネットワークインターフェースの管理、ファイアウォールルールの設定などに利用されます。NetlinkはソケットベースのAPIを提供し、ユーザー空間アプリケーションはNetlinkソケットを通じてカーネルとメッセージを交換します。

RIB (Routing Information Base)

RIBは「ルーティング情報ベース」の略で、ネットワークデバイスがルーティング情報を格納するデータベースです。Netlinkは、このRIBからルーティング情報を取得したり、新しいルーティングエントリを追加したりするために使用されます。

Goの syscall パッケージ

Go言語の syscall パッケージは、オペレーティングシステムが提供する低レベルのシステムコールに直接アクセスするための機能を提供します。これにより、GoプログラムはOSのカーネル機能と直接対話できます。netlink_linux.go のようなファイルは、特定のOS (この場合はLinux) のシステムコールをラップして、Goプログラムから利用できるようにしています。ただし、syscall パッケージは非常に低レベルであり、通常は直接使用するのではなく、より高レベルのライブラリ(例: golang.org/x/sysgithub.com/vishvananda/netlink)を使用することが推奨されています。

Getpagesize()

Getpagesize() は、システムのメモリページサイズをバイト単位で返す関数です。メモリページは、オペレーティングシステムがメモリを管理する最小単位であり、通常は4KB (4096バイト) です。メモリ割り当ての際にページサイズを考慮することは、メモリのアライメントを最適化し、キャッシュの効率を向上させる上で重要になることがあります。

Goのメモリ管理とガベージコレクション (GC)

Go言語は、自動的なガベージコレクション (GC) によってメモリ管理を行います。開発者は手動でメモリを解放する必要がありません。GoのGCは、主に三色マーク・アンド・スイープアルゴリズムを使用し、ほとんどのGC作業はアプリケーションの実行と並行して行われます(コンカレントGC)。これにより、「ストップ・ザ・ワールド (STW)」と呼ばれるアプリケーションの一時停止時間を最小限に抑えるように設計されています。

しかし、頻繁なメモリ割り当てはGCに大きな影響を与えます。

  • GC頻度の増加: 新しいオブジェクトが頻繁に作成されると、ヒープのサイズが急速に増加し、GCサイクルがより頻繁にトリガーされます。
  • CPUオーバーヘッドの増加: GCがより頻繁に実行されると、GC自体の処理に費やされるCPUリソースが増加します。
  • レイテンシースパイクの可能性: GCはほとんどがコンカレントですが、一部のフェーズでは短いSTWポーズが発生します。頻繁なGCは、これらの短いポーズが積み重なり、アプリケーションのレイテンシーに影響を与える可能性があります。
  • GCアシスト: GCがメモリ割り当ての速度に追いつけない場合、実行中のGoroutineがGC作業の一部を肩代わりする「GCアシスト」が発生することがあります。これはアプリケーションの実行速度を低下させます。

したがって、Goプログラムでは、不要なメモリ割り当てを減らすことが、パフォーマンスとメモリ効率を向上させるための重要な最適化戦略となります。

技術的詳細

このコミットの技術的詳細は、Goのメモリ割り当てとGCのオーバーヘッドを削減するための一般的な最適化パターンを適用したものです。

変更前のアプローチ:

func NetlinkRIB(proto, family int) ([]byte, error) {
    // ...
    var tab []byte
done:
    for {
        rb := make([]byte, Getpagesize()) // ループ内で毎回新しいスライスを割り当て
        nr, _, err := Recvfrom(s, rb, 0)
        // ...
    }
    // ...
}

変更前は、for ループの各イテレーションで make([]byte, Getpagesize()) が実行されていました。これは以下の問題を引き起こします。

  1. 新しいスライスの作成: make 関数は、指定されたサイズと容量を持つ新しいバイトスライスをヒープ上に割り当てます。ループのたびにこれが繰り返されると、大量の短期的なオブジェクトが生成され、GCの対象となります。
  2. Getpagesize() の繰り返し呼び出し: Getpagesize() はシステムコールであり、その結果は通常固定です。ループ内で毎回呼び出すことは、不要なCPUサイクルを消費します。

変更後のアプローチ:

func NetlinkRIB(proto, family int) ([]byte, error) {
    // ...
    var tab []byte
    rbNew := make([]byte, Getpagesize()) // ループに入る前に一度だけスライスを割り当て
done:
    for {
        rb := rbNew // 既存のスライスを再利用
        nr, _, err := Recvfrom(s, rb, 0)
        // ...
    }
    // ...
}

変更後では、以下の最適化が行われています。

  1. 事前割り当て (Preallocation): for ループに入る前に rbNew := make([]byte, Getpagesize()) を実行することで、ページサイズのバイトスライスが一度だけヒープに割り当てられます。
  2. スライスの再利用: ループ内では、rb := rbNew とすることで、新しいスライスを割り当てる代わりに、事前に割り当てられた rbNew スライスを rb 変数に代入して再利用します。これにより、ループごとのメモリ割り当てが完全に排除されます。
  3. Getpagesize() 呼び出しの削減: Getpagesize()NetlinkRIB 関数が呼び出される際に一度だけ実行されるようになり、ループ内での繰り返し呼び出しがなくなります。

この変更により、NetlinkRIB 関数が大量のNetlinkメッセージを処理する際のメモリフットプリントが大幅に削減され、GCの負荷が軽減されることで、全体的なパフォーマンスが向上します。コミットメッセージに記載されている「141.5 MBから52 MBへのメモリ削減」という具体的な数値は、この最適化の有効性を示しています。

コアとなるコードの変更箇所

src/pkg/syscall/netlink_linux.go ファイルの NetlinkRIB 関数における変更点です。

--- a/src/pkg/syscall/netlink_linux.go
+++ b/src/pkg/syscall/netlink_linux.go
@@ -64,9 +64,10 @@ func NetlinkRIB(proto, family int) ([]byte, error) {
 		return nil, err
 	}
 	var tab []byte
+	rbNew := make([]byte, Getpagesize())
 done:
 	for {
-		rb := make([]byte, Getpagesize())
+		rb := rbNew
 		nr, _, err := Recvfrom(s, rb, 0)
 		if err != nil {
 			return nil, err

コアとなるコードの解説

変更の核心は、NetlinkRIB 関数内でNetlinkメッセージを受信するために使用されるバイトスライス rb の生成方法にあります。

  1. rbNew := make([]byte, Getpagesize()):

    • この行は、for ループの外側に追加されました。
    • Getpagesize() を呼び出してシステムのページサイズを取得し、そのサイズの新しいバイトスライス rbNew を一度だけ作成します。このスライスは、NetlinkRIB 関数が実行されている間、再利用可能なバッファとして機能します。
  2. rb := rbNew:

    • for ループの内側で、以前 rb := make([]byte, Getpagesize()) だった行が rb := rbNew に変更されました。
    • これにより、ループの各イテレーションで新しいバイトスライスを割り当てる代わりに、事前に作成された rbNew スライスへの参照を rb に代入します。
    • Recvfrom 関数は、この rb スライスに受信したデータを書き込みます。スライスは参照型であるため、rb を介して行われた変更は rbNew にも反映されます。

このシンプルな変更により、ループ内でのメモリ割り当てが完全に排除され、Getpagesize() の呼び出しも NetlinkRIB 関数が呼び出されるたびに1回だけになります。結果として、メモリ使用量が削減され、ガベージコレクションの頻度が低下し、全体的なパフォーマンスが向上します。

関連リンク

  • Go Code Review: https://golang.org/cl/110920043

参考にした情報源リンク

  • Goのメモリ管理とガベージコレクションに関する記事:
    • https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFDcTuAEQrZuBv9hoNhuaM1j8XbGmQ2P8KJSagUUi6GMjN2P0C5pMICoEF8JBonpkJdTBinWUq7ONCUxPn013VnJ3diUKMVnAi2IWpLO0KT0BHTY-d42_xnk3dJQnV4UlMyzvR83MNrihmBiqeSCDKib2Z_Xv5LrM8o7BkC86GY7XvMRZr_qieJbQG6jx1OuK6O5ERz_c4hXrGBu9wG
    • https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE0fXd6lJQzdl2b3oys0zgmt7AuXuNyz2_DzeiJKi8o-LKeDNWfkkWM5zedgOwHTfG9yB-13PE54iI_HTl5G0pl_5DzgXxhRNmSCyN0uKyMm6uNhoIPK_WQlrfSfz5EdRnd52-tsVgWviNYYy28LCWTHyfjeGL--1rhCKFwBUMMlC8BTVP7bva7h259SyBeD3DZxoMXqwMC7OUjgusMaSh4uF5Uh8QhEDcA9-NaMKVOH49HdmRqz4ubiuljBsegHLFAwnMJyOs=
    • https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG3ZzFMvDEQ3OejSuax_aGp6nLw5aLunJhlbkoqzbQBD8XvxI1hrmT0jh08hwAOJYcPwZfOHQj-00_lJj-pFTL_rCxijVLV4q51Ltet6vNW8Cljm8VvBfa33bsJCdRHFXttKggSnQOywoTzl9nFWhjjUoTUgAtYtYOwRBuKUhoEhZ6sj-61yvCz_Z4-Dg184s8Y5SpFhDD6LMBP5qpMLuqoSG57vEo_yRkOSvFMKNEi-hxrTqmukeSxYuNZxHEBnxVxuCYTRA==
  • Goの syscall パッケージとNetlinkに関する情報:
    • https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEWsVIXyPSwD86S5iDGstfrgBnoQw61i4vQ-E_lKv9d4-RT7IZOMBbggaZGVHTrF59SSZFbsNdyQjlntgEJJx8EEAArZmfBW0CU24ollrMJa-yN1463R_gaavctW4NmIuk=
    • https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFgPCntyR-Z6HDhwT6IgkyEhXlxJspO5kuuLmJNIgM2WYN4EN6qTE24iA6sB5CMIWFJ8ibw5Fsfyeq2TAa7vlazX13yQo2Dab44TfsmWQIntHJl4n1xH7InjUxpqb_V5bVb3VKm9VTO6bL08LCIKINw1wIuJmUOCV6x2EAYT9eNIrhw3baQigw=
  • Getpagesize の利用とメモリ最適化に関する一般的な情報:
    • https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHC9oLQKqhpgu4AQFPGhWU8R9GtJI1pPmSPy9aQxewZ9oRkwEPw4WMkBuzigarVGNRNeabEx0zRkumMKz6HUrH78EICvPZ4O-B-mrKhgrKrZ5JqPbNr7n7iUr3XiOpRgOSICmjjYdYIcsaeX3VuDB0XosGOiP-8nIpfmqn4mhkHnWkfLQDlyw==
    • https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHGw-AorX1OaF4MWc-tpg0-_Nk_5B6EqrebYDVtAcBVCH0-BronaCJfVY_EK_A0b2dKKqbkaTNQhsJIuDE71H7i8AO5A2TXjzLiuA17HoAL0AqAfZ744kONjeLot4dRM92m9A==
    • https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHN-QqORCzzBhZVftGqf2tVbBRd0blhK1wj0TMS2MG8VKxO98NcPz1kjmWY43hKp_i0NID6EVN-MsnOYOZQT9neUD52_8i64ahIP48fxz5Mg0sm3nySVPKP5Qqds9y3LGd70VyljLgibbUIRJL4