[インデックス 15242] ファイルの概要
このコミットは、Go言語のテスト実行環境におけるパフォーマンス改善を目的としています。具体的には、os.Getwd
(現在のワーキングディレクトリを取得する関数)の呼び出しコストを削減するために、子プロセスに渡される環境変数$PWD
を適切に設定する変更が加えられています。
コミット
commit 551f3f27aaa69495a499eb3637b79480a2054cbc
Author: Russ Cox <rsc@golang.org>
Date: Thu Feb 14 14:21:26 2013 -0500
test/run: use correct $PWD to make os.Getwd less expensive
The commands being run are 'go tool this' and 'go tool that',
and the go command will call Getwd during its init.
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/7336045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/551f3f27aaa69495a499eb3637b79480a2054cbc
元コミット内容
test/run: use correct $PWD to make os.Getwd less expensive
The commands being run are 'go tool this' and 'go tool that',
and the go command will call Getwd during its init.
変更の背景
Go言語のテストスイートでは、go tool
コマンドを使用して様々なテストが実行されます。これらのgo tool
コマンドは、起動時にos.Getwd
関数を呼び出して現在のワーキングディレクトリを取得します。
os.Getwd
は、システムコールを通じて現在のワーキングディレクトリを問い合わせるため、特に頻繁に呼び出される場合や、ファイルシステムの状態によっては、比較的コストの高い操作となる可能性があります。
このコミットの背景には、テスト実行時のos.Getwd
の呼び出しがパフォーマンスボトルネックになっているという認識がありました。子プロセス(go tool
コマンド)が起動される際に、親プロセスが既に知っているワーキングディレクトリ情報を環境変数$PWD
として渡すことで、子プロセスがos.Getwd
を呼び出す際のオーバーヘッドを削減し、テスト全体の実行速度を向上させることが目的でした。
前提知識の解説
1. os.Getwd
関数 (Go言語)
os.Getwd()
は、Go言語の標準ライブラリos
パッケージに含まれる関数で、現在のワーキングディレクトリの絶対パスを文字列として返します。内部的には、オペレーティングシステムが提供するシステムコール(例: Linux/Unix系ではgetcwd(2)
、WindowsではGetCurrentDirectory
)を呼び出して情報を取得します。
このシステムコールは、ファイルシステムを走査してパスを解決する必要があるため、特に深いディレクトリ階層やネットワークファイルシステム上では、ある程度の時間がかかることがあります。
2. $PWD
環境変数
$PWD
は、多くのUnix系シェル(bash, zshなど)で使われる環境変数で、"Print Working Directory"の略です。この変数は、現在のシェルのワーキングディレクトリの絶対パスを保持しています。
シェルは、cd
コマンドなどでディレクトリを移動するたびに、この$PWD
変数を自動的に更新します。これにより、ユーザーはpwd
コマンドを実行する代わりにecho $PWD
で現在のディレクトリを素早く確認できます。
プログラムが子プロセスを起動する際、親プロセスの環境変数は通常、子プロセスに継承されます。子プロセスは、この$PWD
環境変数を参照することで、システムコールを介さずに自身のワーキングディレクトリを効率的に知ることができます。多くのプログラムやライブラリは、パフォーマンス最適化のために、まず$PWD
環境変数をチェックし、それが存在しないか不正な場合にのみos.Getwd
のようなシステムコールにフォールバックする実装を採用しています。
3. os/exec
パッケージとcmd.Dir
, cmd.Env
Go言語で外部コマンドを実行するには、os/exec
パッケージを使用します。
exec.Command
関数でCmd
構造体を作成し、実行するコマンドと引数を指定します。Cmd.Dir
フィールドは、コマンドを実行するワーキングディレクトリを指定します。このフィールドが設定されていない場合、コマンドは親プロセスと同じワーキングディレクトリで実行されます。Cmd.Env
フィールドは、子プロセスに渡す環境変数を[]string
型で指定します。各要素はKEY=VALUE
形式の文字列です。このフィールドがnil
の場合、子プロセスは親プロセスの環境変数をそのまま継承します。Cmd.Env
が設定されている場合、親プロセスの環境変数は継承されず、指定された環境変数のみが子プロセスに渡されます。
技術的詳細
このコミットは、test/run.go
ファイル内のtest
構造体のrun
メソッドに修正を加えています。run
メソッドは、Goのテストスイートが外部コマンド(特にgo tool
コマンド)を実行する際に使用されます。
変更の核心は、cmd.Dir
が設定される(つまり、一時ディレクトリでコマンドが実行される)場合に、cmd.Env
も明示的に設定し、その中に正しい$PWD
環境変数を含めるようにした点です。
具体的には、以下の新しい関数envForDir
が追加されました。
// envForDir returns a copy of the environment
// suitable for running in the given directory.
// The environment is the current process's environment
// but with an updated $PWD, so that an os.Getwd in the
// child will be faster.
func envForDir(dir string) []string {
env := os.Environ() // 現在のプロセスの環境変数を取得
for i, kv := range env {
if strings.HasPrefix(kv, "PWD=") {
env[i] = "PWD=" + dir // 既存のPWDがあれば更新
return env
}
}
env = append(env, "PWD="+dir) // PWDがなければ追加
return env
}
このenvForDir
関数は、以下のロジックで動作します。
os.Environ()
を呼び出して、現在のプロセスのすべての環境変数を取得します。- 取得した環境変数リストをループし、
PWD=
で始まるエントリを探します。 - もし
PWD=
で始まるエントリが見つかった場合、そのエントリの値を引数dir
で渡された新しいディレクトリパスに更新し、更新された環境変数リストを返します。 PWD=
で始まるエントリが見つからなかった場合、新しいPWD=
エントリを環境変数リストの末尾に追加し、そのリストを返します。
この関数が返す環境変数リストは、cmd.Env
に設定されます。これにより、子プロセスは、os.Getwd
を呼び出す前に、まず環境変数$PWD
から現在のワーキングディレクトリを効率的に取得できるようになります。これにより、システムコールを回避できるため、パフォーマンスが向上します。
コアとなるコードの変更箇所
test/run.go
ファイルに以下の変更が加えられました。
func (t *test) run()
メソッド内のif useTmp
ブロックに、cmd.Env = envForDir(cmd.Dir)
の行が追加されました。- 新しいヘルパー関数
func envForDir(dir string) []string
がファイル末尾に追加されました。
--- a/test/run.go
+++ b/test/run.go
@@ -433,6 +433,7 @@ func (t *test) run() {
cmd.Stderr = &buf
if useTmp {
cmd.Dir = t.tempDir
+ cmd.Env = envForDir(cmd.Dir)
}
err := cmd.Run()
if err != nil {
@@ -828,3 +829,20 @@ func checkShouldTest() {
assertNot(shouldTest("// +build arm 386", "linux", "amd64"))
assert(shouldTest("// This is a test.", "os", "arch"))
}
+
+// envForDir returns a copy of the environment
+// suitable for running in the given directory.
+// The environment is the current process's environment
+// but with an updated $PWD, so that an os.Getwd in the
+// child will be faster.
+func envForDir(dir string) []string {
+ env := os.Environ()
+ for i, kv := range env {
+ if strings.HasPrefix(kv, "PWD=") {
+ env[i] = "PWD=" + dir
+ return env
+ }
+ }
+ env = append(env, "PWD="+dir)
+ return env
+}
コアとなるコードの解説
func (t *test) run()
内の変更
if useTmp {
cmd.Dir = t.tempDir
cmd.Env = envForDir(cmd.Dir) // 追加された行
}
この部分では、テストが一時ディレクトリ(t.tempDir
)で実行される場合に、子プロセスのワーキングディレクトリをt.tempDir
に設定しています。そして、追加された行cmd.Env = envForDir(cmd.Dir)
によって、この一時ディレクトリのパスを$PWD
環境変数として子プロセスに明示的に渡すようにしています。これにより、子プロセスがos.Getwd
を呼び出す際に、環境変数から直接パスを取得できる可能性が高まり、システムコールによるオーバーヘッドを回避できます。
func envForDir(dir string) []string
関数
この関数は、子プロセスに渡すための環境変数リストを生成します。
env := os.Environ()
: 現在のGoプロセスの環境変数(PATH
,HOME
など、親シェルから継承されたもの全て)のコピーを取得します。for i, kv := range env
: 取得した環境変数リストを反復処理します。kv
はKEY=VALUE
形式の文字列です。if strings.HasPrefix(kv, "PWD=")
: 各環境変数がPWD=
で始まるかどうかをチェックします。これは、既存の$PWD
環境変数を見つけるためです。env[i] = "PWD=" + dir
: もし既存の$PWD
が見つかった場合、その値を新しいディレクトリパスdir
で上書きします。return env
: 更新された環境変数リストを即座に返します。これにより、リストの残りの部分を不必要に処理することを避けます。env = append(env, "PWD="+dir)
: ループが終了してもPWD
が見つからなかった場合(つまり、現在のプロセスに$PWD
が設定されていなかった場合)、新しいPWD=
エントリをリストの末尾に追加します。return env
: 最終的な環境変数リストを返します。
この関数は、既存の$PWD
を更新するか、存在しない場合は新しく追加することで、子プロセスが常に正しいワーキングディレクトリ情報を$PWD
経由で利用できるようにします。
関連リンク
- Go言語
os
パッケージ: https://pkg.go.dev/os - Go言語
os/exec
パッケージ: https://pkg.go.dev/os/exec getcwd(2)
- Linux man page: https://man7.org/linux/man-pages/man2/getcwd.2.htmlGetCurrentDirectory
- Windows API: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getcurrentdirectory
参考にした情報源リンク
- Go CL 7336045: https://golang.org/cl/7336045 (コミットメッセージに記載されているGoのコードレビューシステムへのリンク)
- Go言語の公式ドキュメント
- Unix/Linuxのシェルおよび環境変数に関する一般的な知識
- Go言語の
os/exec
パッケージの動作に関する一般的な知識