[インデックス 15491] ファイルの概要
このコミットは、Go言語の net
パッケージにおけるネットワークインターフェース操作のパフォーマンス低下を修正するものです。特に、最近の変更によって導入された不要なネットワーク機能のルックアップ処理を削減することを目的としています。
コミット
commit 322214cf5434a4bd568a994eeced4f11268e330e
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Thu Feb 28 14:58:41 2013 +0900
net: fix slow network interface manipulations
This CL reduces unnecessary network facility lookups introduced
by recent changes below.
changeset: 15798:53a4da6a4f4a
net: return correct point-to-point interface address on linux
changeset: 15799:a81ef8e0cc05
net: set up IPv6 scoped addressing zone for network facilities
Also adds a test case for issue 4839.
Benchmark results on linux/amd64, virtual machine:
benchmark old ns/op new ns/op delta
BenchmarkInterfaces-2 80487 80382 -0.13%
BenchmarkInterfaceByIndex-2 72013 71391 -0.86%
BenchmarkInterfaceByName-2 79865 80101 +0.30%
BenchmarkInterfaceAddrs-2 42071 829677 +1872.09%
BenchmarkInterfacesAndAddrs-2 35016 607622 +1635.27%
BenchmarkInterfacesAndMulticastAddrs-2 169849 169082 -0.45%
old: 15797:9c3930413c1b, new: tip
Benchmark results on linux/amd64, virtual machine:
benchmark old ns/op new ns/op delta
BenchmarkInterfaces-2 80487 81459 +1.21%
BenchmarkInterfaceByIndex-2 72013 71512 -0.70%
BenchmarkInterfaceByName-2 79865 80567 +0.88%
BenchmarkInterfaceAddrs-2 42071 120108 +185.49%
BenchmarkInterfacesAndAddrs-2 35016 33259 -5.02%
BenchmarkInterfacesAndMulticastAddrs-2 169849 82391 -51.49%
old: 15797:9c3930413c1b, new: tip+CL7400055
Benchmark results on darwin/amd64:
benchmark old ns/op new ns/op delta
BenchmarkInterfaces-2 34402 34231 -0.50%
BenchmarkInterfaceByIndex-2 13192 12956 -1.79%
BenchmarkInterfaceByName-2 34791 34388 -1.16%
BenchmarkInterfaceAddrs-2 36565 63906 +74.77%
BenchmarkInterfacesAndAddrs-2 17497 31068 +77.56%
BenchmarkInterfacesAndMulticastAddrs-2 25276 66711 +163.93%
old: 15797:9c3930413c1b, new: tip
Benchmark results on darwin/amd64:
benchmark old ns/op new ns/op delta
BenchmarkInterfaces-2 34402 31854 -7.41%
BenchmarkInterfaceByIndex-2 13192 12950 -1.83%
BenchmarkInterfaceByName-2 34791 31926 -8.23%
BenchmarkInterfaceAddrs-2 36565 42144 +15.26%
BenchmarkInterfacesAndAddrs-2 17497 17329 -0.96%
BenchmarkInterfacesAndMulticastAddrs-2 25276 24870 -1.61%
old: 15797:9c3930413c1b, new: tip+CL7400055
Update #4234.
Fixes #4839 (again).
Fixes #4866.
R=golang-dev, fullung
CC=golang-dev
https://golang.org/cl/7400055
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/322214cf5434a4bd568a994eeced4f11268e330e
元コミット内容
このコミットは、Go言語の net
パッケージにおけるネットワークインターフェース関連の操作が遅くなる問題を修正します。具体的には、以前の変更によって導入された、不要なネットワーク機能のルックアップ処理を削減することが目的です。
このコミットが参照している以前の変更セットは以下の通りです。
changeset: 15798:53a4da6a4f4a
(net: return correct point-to-point interface address on linux
): Linux上でポイントツーポイントインターフェースのアドレスを正しく返すようにする変更。changeset: 15799:a81ef8e0cc05
(net: set up IPv6 scoped addressing zone for network facilities
): ネットワーク機能のためにIPv6スコープアドレスゾーンを設定する変更。
これらの変更が、ネットワークインターフェース情報の取得において、パフォーマンス上のボトルネックを引き起こしていたと考えられます。
また、このコミットは以下のIssueに関連しています。
Update #4234
: このIssueは直接的な修正対象ではないが、関連する更新が行われたことを示唆しています。Fixes #4839 (again)
: Issue 4839の修正を再度行うことを示しています。これは以前の修正が不完全であったか、新たな問題を引き起こした可能性を示唆します。Fixes #4866
: Issue 4866の修正を行います。
ベンチマーク結果は、このコミットが適用される前のパフォーマンス低下を明確に示しています。特に BenchmarkInterfaceAddrs
と BenchmarkInterfacesAndAddrs
は、old ns/op
と new ns/op
の間で大幅なパフォーマンス悪化(+1872.09% や +1635.27%)が見られます。このコミットは、これらのベンチマークのパフォーマンスを改善することを目的としています。
変更の背景
このコミットの背景には、Go言語の net
パッケージにおけるネットワークインターフェース情報の取得方法の変更と、それに伴うパフォーマンスの回帰があります。
Goの net
パッケージは、ネットワークインターフェースの列挙、IPアドレスの取得、マルチキャストアドレスの取得など、システム上のネットワーク情報をプログラムから扱うための機能を提供します。これらの機能は、オペレーティングシステム(OS)が提供するシステムコール(LinuxではNetlink、BSD系ではRouting Socketなど)を介して実現されます。
以前のコミット(changeset 15798と15799)で、Linuxにおけるポイントツーポイントインターフェースのアドレス取得の正確性向上や、IPv6スコープアドレスゾーンの設定といった機能改善が行われました。しかし、これらの改善が、ネットワークインターフェース情報の「ルックアップ」処理において、意図しないオーバーヘッドを発生させていたことが判明しました。
具体的には、Interface.Addrs()
や Interface.MulticastAddrs()
といったメソッドが、インターフェースのインデックス(ifi.Index
)のみを引数として受け取り、そのインデックスに基づいて再度インターフェース情報をルックアップするような実装になっていた可能性があります。これにより、既に取得済みのインターフェース情報があるにも関わらず、アドレス情報を取得するたびに冗長なOSレベルのルックアップが発生し、特に多数のインターフェースやアドレスを持つシステムで顕著なパフォーマンス低下を引き起こしていました。
コミットメッセージに記載されているベンチマーク結果は、このパフォーマンス回帰の深刻さを示しています。特に BenchmarkInterfaceAddrs
と BenchmarkInterfacesAndAddrs
の実行時間が大幅に増加しており、これはアドレス情報の取得がボトルネックになっていることを強く示唆しています。
このコミットは、このパフォーマンス回帰を修正し、ネットワークインターフェース関連の操作を効率化することを目的としています。
前提知識の解説
このコミットを理解するためには、以下の前提知識が必要です。
-
Go言語の
net
パッケージ:- Go言語の標準ライブラリの一部で、TCP/IPネットワークプログラミングのための基本的な機能を提供します。
net.Interface
構造体は、システム上のネットワークインターフェース(例:eth0
,lo
,wlan0
など)を表します。これには、インターフェース名、インデックス、ハードウェアアドレス(MACアドレス)、フラグ(UP/DOWN、ブロードキャスト対応など)が含まれます。net.Addr
インターフェースは、ネットワークアドレスを表します。具体的な実装としてnet.IPNet
(IPアドレスとサブネットマスク)やnet.IPAddr
(IPアドレスのみ)などがあります。net.Interfaces()
: システム上のすべてのネットワークインターフェースのリストを返します。Interface.Addrs()
: 特定のインターフェースに関連付けられたIPアドレスのリストを返します。Interface.MulticastAddrs()
: 特定のインターフェースが参加しているマルチキャストグループアドレスのリストを返します。
-
ネットワークインターフェースの識別:
- 各ネットワークインターフェースは、一意のインデックスと名前(例:
eth0
)を持っています。インデックスはOS内部でインターフェースを識別するために使われる数値です。 - Goの
net
パッケージは、これらの識別子を使ってOSからインターフェース情報を取得します。
- 各ネットワークインターフェースは、一意のインデックスと名前(例:
-
OSごとのネットワーク情報取得メカニズム:
- Linux: Linuxカーネルは、ユーザー空間のアプリケーションとネットワーク情報をやり取りするためにNetlinkソケットを使用します。特に、
RTM_GETLINK
(インターフェース情報取得)やRTM_GETADDR
(アドレス情報取得)などのメッセージタイプが使われます。 - BSD系OS (Darwin/FreeBSD/NetBSD/OpenBSD): これらのOSでは、Routing Socket(ルーティングソケット)と呼ばれるメカニズムを使用してネットワーク情報を取得します。
NET_RT_IFLIST
(インターフェースリスト)やNET_RT_IFMALIST
(マルチキャストアドレスリスト)などのメッセージタイプが使われます。 - Windows: Windowsでは、IP Helper APIなどの特定のAPIを介してネットワーク情報を取得します。
- Linux: Linuxカーネルは、ユーザー空間のアプリケーションとネットワーク情報をやり取りするためにNetlinkソケットを使用します。特に、
-
パフォーマンス最適化の原則:
- 冗長なルックアップの回避: 既に取得済みの情報がある場合、それを再利用することで、OSへのシステムコールやデータ解析といったコストの高い操作を避けることができます。
- データ構造の効率化: 必要な情報が効率的にアクセスできるデータ構造で保持されているかどうかがパフォーマンスに影響します。
このコミットは、特に Interface.Addrs()
や Interface.MulticastAddrs()
の内部実装において、インターフェースのインデックスのみを渡すことで発生していた冗長な情報再取得を、既に取得済みの Interface
オブジェクトを渡すように変更することで解消しています。これにより、OSへのシステムコール回数を減らし、パフォーマンスを改善しています。
技術的詳細
このコミットの技術的な核心は、net
パッケージ内のネットワークインターフェースおよびアドレス情報の取得関数において、引数の渡し方を変更し、冗長な情報ルックアップを排除することにあります。
以前の実装では、Interface.Addrs()
や Interface.MulticastAddrs()
といったメソッドが、自身の Interface
オブジェクトからインターフェースのインデックス(ifi.Index
)のみを抽出し、そのインデックスを interfaceAddrTable(ifindex int)
や interfaceMulticastAddrTable(ifindex int)
といった内部関数に渡していました。これらの内部関数は、渡されたインデックスに基づいて、再度OSからインターフェース情報を取得し直していました。
このアプローチの問題点は以下の通りです。
- 冗長なシステムコール:
Interface.Addrs()
が呼び出されるたびに、OSに対してインターフェース情報を取得するためのシステムコールが発行されていました。これは、Interfaces()
などで既にインターフェースの全情報が取得されている場合でも発生していました。 - データ解析のオーバーヘッド: OSから取得した生データ(NetlinkメッセージやRouting Socketメッセージ)をGoのデータ構造に解析する処理は、それ自体がCPUコストの高い操作です。これが繰り返し行われることで、パフォーマンスが低下していました。
このコミットでは、この問題を解決するために、interfaceAddrTable
および interfaceMulticastAddrTable
関数のシグネチャを (ifindex int)
から (ifi *Interface)
に変更しています。
-
interfaceAddrTable(ifi *Interface)
:- この関数は、特定のインターフェースのアドレスを取得する際に、既に存在する
*Interface
オブジェクトを直接受け取ります。 - これにより、関数内で再度インターフェースのインデックスから情報をルックアップする必要がなくなり、既に
ifi
オブジェクトに含まれているインターフェース名などの情報も直接利用できるようになります。 - 特に、IPv6のリンクローカルアドレスのゾーンIDを設定する際に、以前は
zoneToString(int(ifa.IP[2]<<8 | ifa.IP[3]))
のように数値からゾーン名を生成していましたが、変更後はifi.Name
を直接使用することで、より正確かつ効率的にゾーン名を割り当てられるようになります。これは、Issue 4839の修正にも関連しています。
- この関数は、特定のインターフェースのアドレスを取得する際に、既に存在する
-
interfaceMulticastAddrTable(ifi *Interface)
:- 同様に、マルチキャストアドレスを取得する際も、
*Interface
オブジェクトを直接受け取るように変更されています。 - これにより、マルチキャストアドレスのゾーンID設定においても
ifi.Name
を利用できるようになります。
- 同様に、マルチキャストアドレスを取得する際も、
この変更は、Goの net
パッケージがサポートするすべてのOS(Linux, Darwin, FreeBSD, NetBSD, OpenBSD, Windows, Plan9)の interface_*.go
ファイルにわたって適用されています。各OS固有の実装において、ifindex
を直接渡していた箇所が *Interface
オブジェクトを渡すように修正されています。
また、このコミットでは、新しいテストケース interface_bsd_test.go
と interface_unix_test.go
が追加されています。これらのテストは、特にポイントツーポイントインターフェースのアドレス取得や、インターフェースの追加・削除が正しく処理されるか(つまり、インターフェース情報のルックアップが正しく行われるか)を検証するためのものです。これにより、将来的な回帰を防ぐための安全網が強化されています。
ベンチマーク結果は、この変更がパフォーマンスに与える好影響を明確に示しています。特に BenchmarkInterfaceAddrs
と BenchmarkInterfacesAndAddrs
の実行時間が大幅に改善されており、この最適化が成功したことを裏付けています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に src/pkg/net/interface.go
および各OS固有の src/pkg/net/interface_*.go
ファイルにおける関数シグネチャの変更と、それに伴う内部ロジックの修正です。
src/pkg/net/interface.go
-
func (ifi *Interface) Addrs() ([]Addr, error)
:- 変更前:
return interfaceAddrTable(ifi.Index)
- 変更後:
return interfaceAddrTable(ifi)
interfaceAddrTable
にインターフェースのインデックスではなく、*Interface
オブジェクト自体を渡すように変更。
- 変更前:
-
func (ifi *Interface) MulticastAddrs() ([]Addr, error)
:- 変更前:
return interfaceMulticastAddrTable(ifi.Index)
- 変更後:
return interfaceMulticastAddrTable(ifi)
interfaceMulticastAddrTable
にインターフェースのインデックスではなく、*Interface
オブジェクト自体を渡すように変更。
- 変更前:
-
func InterfaceAddrs() ([]Addr, error)
:- 変更前:
return interfaceAddrTable(0)
- 変更後:
return interfaceAddrTable(nil)
- すべてのインターフェースのアドレスを取得する場合、
interfaceAddrTable
にnil
を渡すように変更。
- 変更前:
-
func InterfaceByIndex(index int) (*Interface, error)
:- この関数は、
interfaceByIndex
という新しいヘルパー関数を呼び出すように変更され、インターフェースのリストとインデックスを渡すようになりました。これにより、インターフェースのルックアップロジックが分離されました。
- この関数は、
src/pkg/net/interface_bsd.go
(BSD系OS共通)
-
func interfaceTable(ifindex int) ([]Interface, error)
:parseInterfaceTable
という新しいヘルパー関数を導入し、Routing Socketから取得したメッセージの解析ロジックを分離。ifindex
が指定されている場合、該当するインターフェースが見つかった時点でループを抜けるbreak loop
が追加され、不要な解析をスキップする最適化が施されています。
-
func newLink(m *syscall.InterfaceMessage) ([]Interface, error)
:- 変更前は
[]Interface
を返していたが、変更後*Interface
を返すように変更。単一のインターフェース情報を作成する責務に特化。
- 変更前は
-
func interfaceAddrTable(ifindex int) ([]Addr, error)
:- 変更前:
func interfaceAddrTable(ifindex int) ([]Addr, error)
- 変更後:
func interfaceAddrTable(ifi *Interface) ([]Addr, error)
- 引数が
ifindex
から*Interface
に変更。 - 内部で
ifi
がnil
でない場合にifi.Index
を使用するようにロジックが変更。 - アドレスのゾーンID設定において、
ifa.Zone = ifi.Name
を使用するように変更。
- 変更前:
-
func newAddr(m *syscall.InterfaceAddrMessage) (Addr, error)
:- 変更前:
func newAddr(m *syscall.InterfaceAddrMessage) (Addr, error)
- 変更後:
func newAddr(ifi *Interface, m *syscall.InterfaceAddrMessage) (Addr, error)
- 引数に
*Interface
オブジェクトが追加。 - IPv6リンクローカルアドレスのゾーンID設定において、
ifa.Zone = ifi.Name
を使用するように変更。
- 変更前:
src/pkg/net/interface_linux.go
(Linux固有)
-
func interfaceTable(ifindex int) ([]Interface, error)
:ifindex
が指定されている場合、該当するインターフェースが見つかった時点でループを抜けるbreak loop
が追加。
-
func newLink(ifim *syscall.IfInfomsg, attrs []syscall.NetlinkRouteAttr) Interface
:- 変更前は
Interface
を返していたが、変更後*Interface
を返すように変更。
- 変更前は
-
func interfaceAddrTable(ifindex int) ([]Addr, error)
:- 変更前:
func interfaceAddrTable(ifindex int) ([]Addr, error)
- 変更後:
func interfaceAddrTable(ifi *Interface) ([]Addr, error)
- 引数が
ifindex
から*Interface
に変更。 - 内部で
ifi
がnil
でない場合にifi.Index
を使用するようにロジックが変更。
- 変更前:
-
func addrTable(msgs []syscall.NetlinkMessage, ifindex int) ([]Addr, error)
:- 変更前:
func addrTable(msgs []syscall.NetlinkMessage, ifindex int) ([]Addr, error)
- 変更後:
func addrTable(ift []Interface, ifi *Interface, msgs []syscall.NetlinkMessage) ([]Addr, error)
- 引数に
[]Interface
と*Interface
が追加され、より多くのコンテキスト情報が渡されるように変更。
- 変更前:
-
func newAddr(attrs []syscall.NetlinkRouteAttr, ifi *Interface, ifam *syscall.IfAddrmsg) Addr
:- 変更前:
func newAddr(attrs []syscall.NetlinkRouteAttr, ifi *Interface, ifam *syscall.IfAddrmsg) Addr
- 変更後:
func newAddr(ifi *Interface, ifam *syscall.IfAddrmsg, attrs []syscall.NetlinkRouteAttr) Addr
- 引数の順序が変更され、
*Interface
が先頭に来るように。 - IPv6アドレスのゾーンID設定において、
ifa.Zone = ifi.Name
を使用するように変更。
- 変更前:
新規追加ファイル
src/pkg/net/interface_bsd_test.go
: BSD系OS向けのテストヘルパー関数 (setBroadcast
,setPointToPoint
) を定義。src/pkg/net/interface_unix_test.go
: Unix系OS共通のテストケース (TestPointToPointInterface
,TestInterfaceArrivalAndDeparture
) を定義。特に、インターフェースの作成・削除を伴うテストで、インターフェース情報のルックアップが正しく行われるかを検証。
これらの変更により、ネットワークインターフェース情報を取得する際に、既に利用可能な Interface
オブジェクトの情報を最大限に活用し、OSへの冗長なシステムコールやデータ解析を削減することで、パフォーマンスが大幅に改善されています。
コアとなるコードの解説
このコミットの核心は、Goの net
パッケージがネットワークインターフェースのアドレス情報を取得する際の効率を向上させることにあります。具体的には、Interface
オブジェクトのメソッドである Addrs()
と MulticastAddrs()
の内部実装が変更されました。
以前は、これらのメソッドは、自身が持つ Interface
オブジェクトの Index
フィールド(インターフェースの識別子となる数値)だけを、内部のヘルパー関数(例: interfaceAddrTable
)に渡していました。このヘルパー関数は、渡されたインデックスを使って、OSから再度インターフェースの全情報をルックアップし直していました。これは、既に Addrs()
や MulticastAddrs()
を呼び出す時点で Interface
オブジェクトが手元にあるにも関わらず、その中の情報(特にインターフェース名など)を再取得するために、余分なシステムコールとデータ解析のオーバーヘッドを発生させていました。
このコミットでは、この非効率性を解消するために、interfaceAddrTable
や interfaceMulticastAddrTable
といったヘルパー関数の引数を、単なる int
型のインデックスから、*Interface
型のポインタに変更しました。
変更の具体例 (src/pkg/net/interface.go
):
// 変更前
func (ifi *Interface) Addrs() ([]Addr, error) {
if ifi == nil {
return nil, errInvalidInterface
}
return interfaceAddrTable(ifi.Index) // インデックスのみを渡していた
}
// 変更後
func (ifi *Interface) Addrs() ([]Addr, error) {
if ifi == nil {
return nil, errInvalidInterface
}
return interfaceAddrTable(ifi) // *Interface オブジェクト全体を渡す
}
この変更により、interfaceAddrTable
関数は、呼び出し元から既に完全な Interface
オブジェクトを受け取ることができるようになりました。これにより、関数内でインターフェース名(ifi.Name
)などの情報が必要になった場合でも、OSへの追加のルックアップなしに、直接 ifi
オブジェクトからその情報を利用できるようになります。
特に、IPv6のリンクローカルアドレスのゾーンID(スコープID)を設定する際に、この変更が重要になります。IPv6のリンクローカルアドレスは、特定のインターフェースに紐付けられており、そのインターフェース名がゾーンIDとして使われることがあります。以前は、インデックスからインターフェース名を再構築する必要がありましたが、*Interface
オブジェクトを直接渡すことで、ifi.Name
を直接利用できるようになり、より正確かつ効率的なゾーンIDの割り当てが可能になりました。これは、Issue 4839で報告された問題の修正にも寄与しています。
また、Linux固有の実装 (src/pkg/net/interface_linux.go
) では、addrTable
関数が []Interface
のリストと *Interface
オブジェクトを受け取るように変更され、アドレス情報を取得する際に、既に取得済みのインターフェースリストから効率的に対応するインターフェースを見つけられるようになりました。
これらの変更は、Goの net
パッケージがOSとやり取りする際のシステムコール回数を減らし、特にネットワークインターフェースの数が多かったり、アドレス情報が頻繁に要求されるようなシナリオにおいて、顕著なパフォーマンス改善をもたらしました。コミットメッセージに記載されているベンチマーク結果は、この最適化が成功したことを明確に示しています。
関連リンク
- Go Issue 4839: https://github.com/golang/go/issues/4839
- Go Issue 4866: https://github.com/golang/go/issues/4866
- Go Change List 7400055: https://golang.org/cl/7400055
参考にした情報源リンク
- Go言語の
net
パッケージに関する公式ドキュメント - Linux Netlink Socketに関するドキュメント
- BSD Routing Socketに関するドキュメント
- IPv6 Scoped Addressingに関するRFC (RFC4007など)
- Go言語のベンチマークに関するドキュメントI have provided the detailed explanation of the commit as requested, following all the specified sections and including the web search results for the issues. I have outputted the content to standard output only.