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

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

このコミットは、Go言語の標準ライブラリである net パッケージにおけるネットワークインターフェースの識別に関するベンチマークを追加するものです。これにより、Interfaces(), InterfaceByIndex(), InterfaceByName(), InterfaceAddrs(), Interface.Addrs(), Interface.MulticastAddrs() といった関数のパフォーマンス特性を測定し、将来的な最適化の基礎データを提供します。

コミット

commit 0ad88a481de7aeba15e5a8b3ee5af7141396b082
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Fri Feb 22 01:19:04 2013 +0900

    net: add benchmarks for network interface identification
    
    Current results on linux/amd64:
    BenchmarkInterfaces                      20000             80902 ns/op
    BenchmarkInterfaceByIndex                50000             71591 ns/op
    BenchmarkInterfaceByName                 20000             79908 ns/op
    BenchmarkInterfaceAddrs                   2000            836413 ns/op
    BenchmarkInterfacesAndAddrs               5000            605946 ns/op
    BenchmarkInterfacesAndMulticastAddrs     10000            169029 ns/op
    
    Update #4866.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/7368046
---
 src/pkg/net/interface_test.go | 83 +++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 81 insertions(+), 2 deletions(-)

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

https://github.com/golang/go/commit/0ad88a481de7aeba15e5a8b3ee5af7141396b082

元コミット内容

このコミットは、src/pkg/net/interface_test.go ファイルにベンチマーク関数を追加しています。具体的には、以下のベンチマークが追加されました。

  • BenchmarkInterfaces: システム上のすべてのネットワークインターフェースを取得する net.Interfaces() 関数のベンチマーク。
  • BenchmarkInterfaceByIndex: インデックスによってネットワークインターフェースを取得する net.InterfaceByIndex() 関数のベンチマーク。
  • BenchmarkInterfaceByName: 名前によってネットワークインターフェースを取得する net.InterfaceByName() 関数のベンチマーク。
  • BenchmarkInterfaceAddrs: システム上のすべてのネットワークインターフェースのアドレスを取得する net.InterfaceAddrs() 関数のベンチマーク。
  • BenchmarkInterfacesAndAddrs: 特定のネットワークインターフェースのすべてのアドレスを取得する Interface.Addrs() メソッドのベンチマーク。
  • BenchmarkInterfacesAndMulticastAddrs: 特定のネットワークインターフェースのマルチキャストアドレスを取得する Interface.MulticastAddrs() メソッドのベンチマーク。

また、ベンチマークの実行に必要なループバックインターフェースを検索するためのヘルパー関数 loopbackInterface() も追加されています。

コミットメッセージには、linux/amd64 環境でのベンチマーク結果の例も含まれており、各操作の平均実行時間(ナノ秒/操作)が示されています。

変更の背景

この変更の背景には、Go言語の net パッケージにおけるネットワークインターフェース関連の操作のパフォーマンス特性を理解し、将来的な最適化の機会を特定するという目的があります。コミットメッセージに Update #4866 とあることから、GoのIssueトラッカーで報告された特定の課題(おそらくパフォーマンスに関するもの)に対応していることが示唆されます。

ネットワークアプリケーションやシステムツールでは、ネットワークインターフェースの情報(IPアドレス、MACアドレス、フラグなど)を頻繁に取得する必要があります。これらの操作のパフォーマンスがアプリケーション全体の応答性に影響を与える可能性があるため、ベンチマークを通じて現状を把握し、ボトルネックを特定することは非常に重要です。

特に、Interfaces()InterfaceAddrs() のような関数は、システムコールを伴うことが多く、その実行時間はOSやハードウェア、ネットワーク構成に大きく依存します。ベンチマークを追加することで、これらの関数のオーバーヘッドを定量的に測定し、パフォーマンスの回帰を検出したり、改善策を検討したりするための基準点が得られます。

前提知識の解説

Go言語のベンチマーク

Go言語には、標準でベンチマークを記述・実行するためのフレームワークが組み込まれています。testing パッケージの一部として提供されており、go test -bench=. コマンドで実行できます。

  • ベンチマーク関数: BenchmarkXxx(*testing.B) というシグネチャを持つ関数として定義されます。
  • *testing.B: ベンチマーク実行のためのコンテキストを提供します。
    • b.N: ベンチマーク関数が実行されるイテレーション回数。Goのテストフレームワークが自動的に調整し、統計的に有意な結果が得られるようにします。
    • b.ResetTimer(): タイマーをリセットします。セットアップコードの実行時間を測定から除外するために使用されます。
    • b.StopTimer(): タイマーを停止します。
    • b.StartTimer(): タイマーを再開します。
    • b.Fatalf(): エラーが発生した場合にベンチマークを停止し、エラーメッセージを出力します。
  • 実行: go test -bench=. コマンドを実行すると、ベンチマーク関数が複数回実行され、各操作にかかる平均時間(ns/op)や1秒あたりの操作数(op/s)などの統計情報が出力されます。

net パッケージとネットワークインターフェース

Go言語の net パッケージは、ネットワークI/Oのプリミティブを提供します。これには、TCP/IP、UDP、DNSルックアップ、そしてネットワークインターフェースの管理などが含まれます。

  • net.Interface 構造体: ネットワークインターフェースの情報を表す構造体です。
    • Index: インターフェースのシステム固有のインデックス。
    • MTU: 最大転送単位。
    • Name: インターフェースの名前(例: "eth0", "lo")。
    • HardwareAddr: ハードウェアアドレス(MACアドレス)。
    • Flags: インターフェースのフラグ(例: FlagUp, FlagLoopback, FlagMulticast)。
  • net.Interfaces(): システム上のすべてのネットワークインターフェースのリストを返します。
  • net.InterfaceByIndex(index int): 指定されたインデックスを持つネットワークインターフェースを返します。
  • net.InterfaceByName(name string): 指定された名前を持つネットワークインターフェースを返します。
  • net.InterfaceAddrs(): システム上のすべてのネットワークインターフェースに関連付けられたユニキャストIPアドレスのリストを返します。
  • (*net.Interface).Addrs(): 特定のネットワークインターフェースに関連付けられたユニキャストIPアドレスのリストを返します。
  • (*net.Interface).MulticastAddrs(): 特定のネットワークインターフェースに関連付けられたマルチキャストIPアドレスのリストを返します。

これらの関数は、通常、OSのネットワークスタックと対話するためにシステムコールを使用します。そのため、パフォーマンスはOSの効率性やシステム上のネットワークインターフェースの数に大きく影響されます。

技術的詳細

このコミットで追加されたベンチマークは、net パッケージのネットワークインターフェース関連のAPIが、一般的な使用シナリオでどの程度のパフォーマンスを発揮するかを測定することを目的としています。

loopbackInterface() ヘルパー関数

ベンチマーク関数の一部(BenchmarkInterfaceByIndex, BenchmarkInterfaceByName, BenchmarkInterfacesAndAddrs, BenchmarkInterfacesAndMulticastAddrs)では、特定のネットワークインターフェース、特にループバックインターフェースを対象としています。これは、テスト環境に依存せず、常に存在するインターフェースであるため、ベンチマークの安定性を高めるためです。

loopbackInterface() 関数は、Interfaces() を呼び出してすべてのインターフェースを取得し、その中から FlagLoopbackFlagUp フラグが設定されているインターフェースを検索して返します。これにより、ベンチマークが有効なインターフェースに対して実行されることが保証されます。

ベンチマークの設計

各ベンチマーク関数は、b.N 回のループ内で対象のAPIを呼び出し、その実行時間を測定します。エラーが発生した場合は b.Fatalf() を呼び出してベンチマークを失敗させます。

  • BenchmarkInterfaces: Interfaces() はシステム上のすべてのインターフェースを列挙するため、インターフェースの数が多いシステムでは時間がかかる可能性があります。
  • BenchmarkInterfaceByIndex / BenchmarkInterfaceByName: これらの関数は特定のインターフェースを検索するため、検索アルゴリズムの効率性が重要になります。
  • BenchmarkInterfaceAddrs: InterfaceAddrs() はすべてのインターフェースのアドレスを列挙するため、インターフェースの数と各インターフェースに割り当てられたアドレスの数に比例して時間がかかります。
  • BenchmarkInterfacesAndAddrs / BenchmarkInterfacesAndMulticastAddrs: これらのベンチマークは、特定のインターフェースのIPアドレスやマルチキャストアドレスを取得する際のパフォーマンスを測定します。これは、Interface オブジェクトが既に取得されている状況での追加情報取得のコストを評価するのに役立ちます。

コミットメッセージに示されているベンチマーク結果は、InterfaceAddrs が最も時間がかかり、次いで InterfacesAndAddrs が続くことを示しています。これは、アドレス情報の取得がインターフェースの列挙よりもコストが高いことを示唆しています。

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

変更はすべて src/pkg/net/interface_test.go ファイル内で行われています。

--- a/src/pkg/net/interface_test.go
+++ b/src/pkg/net/interface_test.go
@@ -9,6 +9,21 @@ import (
  	"testing"
  )
  
+// LoopbackInterface returns a logical network interface for loopback
+// tests.
+func loopbackInterface() *Interface {
+	ift, err := Interfaces()
+	if err != nil {
+		return nil
+	}
+	for _, ifi := range ift {
+		if ifi.Flags&FlagLoopback != 0 && ifi.Flags&FlagUp != 0 {
+			return &ifi
+		}
+	}
+	return nil
+}
+
  func sameInterface(i, j *Interface) bool {
  	if i == nil || j == nil {
  		return false
@@ -29,10 +44,10 @@ func TestInterfaces(t *testing.T) {
  	for _, ifi := range ift {
  		ifxi, err := InterfaceByIndex(ifi.Index)
  		if err != nil {
-			t.Fatalf("InterfaceByIndex(%q) failed: %v", ifi.Index, err)
+			t.Fatalf("InterfaceByIndex(%v) failed: %v", ifi.Index, err)
  		}
  		if !sameInterface(ifxi, &ifi) {
-			t.Fatalf("InterfaceByIndex(%q) = %v, want %v", ifi.Index, *ifxi, ifi)
+			t.Fatalf("InterfaceByIndex(%v) = %v, want %v", ifi.Index, *ifxi, ifi)
  		}
  		ifxn, err := InterfaceByName(ifi.Name)
  		if err != nil {
@@ -102,3 +117,67 @@ func testMulticastAddrs(t *testing.T, ifmat []Addr) {
  		}
  	}
  }\n+\n+func BenchmarkInterfaces(b *testing.B) {\n+\tfor i := 0; i < b.N; i++ {\n+\t\tif _, err := Interfaces(); err != nil {\n+\t\t\tb.Fatalf(\"Interfaces failed: %v\", err)\n+\t\t}\n+\t}\n+}\n+\n+func BenchmarkInterfaceByIndex(b *testing.B) {\n+\tifi := loopbackInterface()\n+\tif ifi == nil {\n+\t\treturn\n+\t}\n+\tfor i := 0; i < b.N; i++ {\n+\t\tif _, err := InterfaceByIndex(ifi.Index); err != nil {\n+\t\t\tb.Fatalf(\"InterfaceByIndex failed: %v\", err)\n+\t\t}\n+\t}\n+}\n+\n+func BenchmarkInterfaceByName(b *testing.B) {\n+\tifi := loopbackInterface()\n+\tif ifi == nil {\n+\t\treturn\n+\t}\n+\tfor i := 0; i < b.N; i++ {\n+\t\tif _, err := InterfaceByName(ifi.Name); err != nil {\n+\t\t\tb.Fatalf(\"InterfaceByName failed: %v\", err)\n+\t\t}\n+\t}\n+}\n+\n+func BenchmarkInterfaceAddrs(b *testing.B) {\n+\tfor i := 0; i < b.N; i++ {\n+\t\tif _, err := InterfaceAddrs(); err != nil {\n+\t\t\tb.Fatalf(\"InterfaceAddrs failed: %v\", err)\n+\t\t}\n+\t}\n+}\n+\n+func BenchmarkInterfacesAndAddrs(b *testing.B) {\n+\tifi := loopbackInterface()\n+\tif ifi == nil {\n+\t\treturn\n+\t}\n+\tfor i := 0; i < b.N; i++ {\n+\t\tif _, err := ifi.Addrs(); err != nil {\n+\t\t\tb.Fatalf(\"Interface.Addrs failed: %v\", err)\n+\t\t}\n+\t}\n+}\n+\n+func BenchmarkInterfacesAndMulticastAddrs(b *testing.B) {\n+\tifi := loopbackInterface()\n+\tif ifi == nil {\n+\t\treturn\n+\t}\n+\tfor i := 0; i < b.N; i++ {\n+\t\tif _, err := ifi.MulticastAddrs(); err != nil {\n+\t\t\tb.Fatalf(\"Interface.MulticastAddrs failed: %v\", err)\n+\t\t}\n+\t}\n+}\n```

主な変更点は以下の通りです。

1.  `loopbackInterface()` 関数の追加。
2.  `TestInterfaces` 関数内の `t.Fatalf` メッセージのフォーマット変更(`%q` から `%v` へ)。これは、`ifi.Index` が文字列ではなく整数であるため、より適切なフォーマット指定子に変更されたものです。
3.  6つの新しいベンチマーク関数の追加。

## コアとなるコードの解説

### `loopbackInterface()`

```go
func loopbackInterface() *Interface {
	ift, err := Interfaces()
	if err != nil {
		return nil
	}
	for _, ifi := range ift {
		if ifi.Flags&FlagLoopback != 0 && ifi.Flags&FlagUp != 0 {
			return &ifi
		}
	}
	return nil
}

この関数は、システム上のすべてのネットワークインターフェースを取得し、その中からループバックインターフェース(FlagLoopback フラグが設定されており、かつ FlagUp フラグも設定されている、つまり有効な状態のインターフェース)を検索して返します。ベンチマークの安定性を確保するために、特定のインターフェースに依存するベンチマークで利用されます。

ベンチマーク関数群

各ベンチマーク関数は、Goのベンチマークフレームワークの慣例に従って記述されています。

func BenchmarkInterfaces(b *testing.B) {
	for i := 0; i < b.N; i++ {
		if _, err := Interfaces(); err != nil {
			b.Fatalf("Interfaces failed: %v", err)
		}
	}
}

この例では、BenchmarkInterfaces 関数が Interfaces() 関数を b.N 回呼び出し、その合計実行時間を測定します。b.N はベンチマーク実行時に自動的に調整されるため、開発者はループの回数を手動で設定する必要がありません。エラーハンドリングも含まれており、API呼び出しが失敗した場合はベンチマークが停止します。

他のベンチマーク関数も同様の構造を持ち、それぞれが対象とする net パッケージのAPIを b.N 回実行します。BenchmarkInterfaceByIndexBenchmarkInterfaceByName など、特定のインターフェースを必要とするベンチマークでは、事前に loopbackInterface() を呼び出して対象のインターフェースを取得しています。これにより、インターフェースの検索自体がベンチマークの対象とならないように配慮されています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード
  • Gitコミットメッセージ
  • GitHubのコミットページ
  • Go言語のベンチマークに関する一般的な知識