[インデックス 15243] ファイルの概要
このコミットは、Goコマンド(cmd/go
)が外部コマンドを実行する際に、そのコマンドの環境変数に$PWD
(現在の作業ディレクトリ)を設定する変更を導入しています。これにより、実行される外部コマンド内でos.Getwd
関数が呼び出された際のパフォーマンスが大幅に向上します。
コミット
commit 357a18a2c625d06f0f9202142b3432458368484f
Author: Russ Cox <rsc@golang.org>
Date: Thu Feb 14 14:21:44 2013 -0500
cmd/go: set $PWD when running commands
This makes os.Getwd inside those commands much faster.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/7324055
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/357a18a2c625d06f0f9202142b3432458368484f
元コミット内容
cmd/go
: コマンド実行時に$PWD
を設定する。
これにより、それらのコマンド内でのos.Getwd
がはるかに高速になる。
変更の背景
Goのビルドシステムやテスト実行、VCS(バージョン管理システム)操作など、cmd/go
が内部的に外部コマンドを呼び出す際、その外部コマンドが自身の現在の作業ディレクトリ(CWD)を取得するためにos.Getwd
のようなシステムコールを使用することがあります。
従来のGoコマンドの実装では、外部コマンドを実行する際に、そのコマンドの環境変数に明示的に$PWD
を設定していませんでした。os.Getwd
は、通常、現在の作業ディレクトリを決定するためにシステムコール(例: Linuxのgetcwd
)を発行します。このシステムコールは、ファイルシステムを走査する必要があるため、特に深いディレクトリ階層やネットワークファイルシステム上では、比較的コストの高い操作となる可能性があります。
このコミットの背景には、os.Getwd
の呼び出しが頻繁に行われるシナリオにおいて、そのパフォーマンスを改善したいという意図があります。$PWD
環境変数を設定することで、多くのシステムではos.Getwd
がシステムコールを発行する代わりに、この環境変数の値を利用して現在の作業ディレクトリを迅速に取得できるようになります。これにより、Goコマンドが起動する外部プロセス全体の効率が向上します。
前提知識の解説
os.Getwd
関数
Go言語の標準ライブラリos
パッケージに含まれるGetwd()
関数は、現在の作業ディレクトリ(Current Working Directory, CWD)の絶対パスを返します。この関数は、プログラムがファイルシステム上のどこで実行されているかを知るために広く使用されます。内部的には、オペレーティングシステムが提供するAPI(例: POSIXシステムにおけるgetcwd()
)を呼び出してCWDを取得します。
環境変数 $PWD
PWD
は、多くのUnix系オペレーティングシステムで使用される標準的な環境変数です。この変数は、現在のシェルセッションの作業ディレクトリの絶対パスを保持します。シェルは、cd
コマンドなどでディレクトリを変更するたびに、このPWD
変数を自動的に更新します。
多くのプログラムやライブラリは、os.Getwd
のようなCWD取得関数を呼び出す際に、まず$PWD
環境変数の値をチェックします。もし$PWD
が設定されており、その値が有効なディレクトリを指していれば、システムコールを発行するよりも高速にCWDを取得できる場合があります。これは、システムコールがカーネルモードへの切り替えやファイルシステムのスキャンを伴うのに対し、環境変数の読み取りはユーザーモードで完結するためです。
cmd/go
の動作と外部コマンドの実行
cmd/go
は、Go言語のビルド、テスト、依存関係管理などを行うための主要なコマンドラインツールです。このツールは、その機能の一部として、コンパイラ(go tool compile
)、リンカ(go tool link
)、テストバイナリ、バージョン管理システム(Git, Mercurialなど)のクライアントなど、様々な外部コマンドを起動します。
Go言語では、os/exec
パッケージを使用して外部コマンドを実行します。exec.Command
構造体には、実行するコマンド、引数、作業ディレクトリ(Dir
フィールド)、環境変数(Env
フィールド)などを設定できます。このコミット以前は、cmd/go
が外部コマンドを起動する際に、Env
フィールドに$PWD
を明示的に設定していませんでした。
技術的詳細
このコミットの核心は、cmd/go
が外部コマンドを実行する際に、そのコマンドの環境変数リストに現在の作業ディレクトリを示すPWD
変数を追加することです。
具体的には、src/cmd/go/main.go
にenvForDir
という新しいヘルパー関数が導入されました。この関数の目的は、指定されたディレクトリdir
に対して適切な環境変数のリストを生成することです。
envForDir
関数は以下のロジックで動作します。
- まず、現在のプロセスの環境変数リスト(
os.Environ()
)を取得します。 - このリストを走査し、既存の
PWD=
で始まるエントリを探します。 - もし
PWD=
エントリが見つかった場合、その値を引数で渡されたdir
のパスに更新します。 PWD=
エントリが見つからなかった場合、新しいPWD=
エントリを"PWD=" + dir
として環境変数リストの末尾に追加します。- 更新または追加された環境変数リストを返します。
このenvForDir
関数は、cmd/go
が外部コマンドを実行する様々な箇所で利用されるようになりました。具体的には、以下のファイルでcmd.Env = envForDir(cmd.Dir)
という形で適用されています。
src/cmd/go/build.go
: ビルドプロセス中に外部ツール(コンパイラ、リンカなど)を実行するbuilder.runOut
メソッド内で使用されます。src/cmd/go/test.go
: テストバイナリを実行するbuilder.runTest
メソッド内で使用されます。src/cmd/go/vcs.go
: バージョン管理システム(Gitなど)のコマンドを実行するvcsCmd.run1
メソッド内で使用されます。
これにより、cmd/go
によって起動されるすべての外部プロセスは、その実行環境に正しい$PWD
環境変数を持つことになります。結果として、これらの外部プロセス内でos.Getwd
が呼び出された際に、システムコールを介さずに$PWD
から直接現在の作業ディレクトリを取得できる可能性が高まり、パフォーマンスが向上します。
この変更は、特にGoのビルドやテストが頻繁に実行されるCI/CD環境や、大規模なGoプロジェクトにおいて、ビルド時間の短縮に貢献する可能性があります。
コアとなるコードの変更箇所
このコミットでは、主に以下の4つのファイルが変更されています。
src/cmd/go/build.go
src/cmd/go/main.go
src/cmd/go/test.go
src/cmd/go/vcs.go
src/cmd/go/build.go
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -1226,7 +1226,7 @@ func (b *builder) runOut(dir string, desc string, cmdargs ...interface{}) ([]byt
cmd.Stdout = &buf
cmd.Stderr = &buf
cmd.Dir = dir
- // TODO: cmd.Env
+ cmd.Env = envForDir(cmd.Dir)
err := cmd.Run()
// cmd.Run will fail on Unix if some other process has the binary
builder.runOut
関数内で、cmd.Env
にenvForDir(cmd.Dir)
を設定する行が追加されました。これにより、ビルド関連の外部コマンド実行時にPWD
が設定されます。
src/cmd/go/main.go
--- a/src/cmd/go/main.go
+++ b/src/cmd/go/main.go
@@ -379,6 +379,25 @@ func runOut(dir string, cmdargs ...interface{}) []byte {
return out
}
+// 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
+ }
+ }
+ // Internally we only use rooted paths, so dir is rooted.
+ // Even if dir is not rooted, no harm done.
+ env = append(env, "PWD="+dir)
+ return env
+}
+
// matchPattern(pattern)(name) reports whether
// name matches pattern. Pattern is a limited glob
// pattern in which '...' means 'any string' and there
envForDir
という新しいヘルパー関数が追加されました。この関数は、指定されたディレクトリdir
に基づいてPWD
環境変数を適切に設定した環境変数リストを返します。
src/cmd/go/test.go
--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -635,6 +635,7 @@ func (b *builder) runTest(a *action) error {
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = a.p.Dir
+ cmd.Env = envForDir(cmd.Dir)
var buf bytes.Buffer
if testStreamOutput {
cmd.Stdout = os.Stdout
@@ -647,7 +648,7 @@ func (b *builder) runTest(a *action) error {
// If there are any local SWIG dependencies, we want to load
// the shared library from the build directory.
if a.p.usesSwig() {
- env := os.Environ()
+ env := cmd.Env
found := false
prefix := "LD_LIBRARY_PATH="
for i, v := range env {
builder.runTest
関数内で、cmd.Env
にenvForDir(cmd.Dir)
を設定する行が追加されました。また、SWIG関連の環境変数設定箇所で、既存のos.Environ()
の代わりにcmd.Env
(すでにPWD
が設定されているもの)を使用するように変更されています。
src/cmd/go/vcs.go
--- a/src/cmd/go/vcs.go
+++ b/src/cmd/go/vcs.go
@@ -190,6 +190,7 @@ func (v *vcsCmd) run1(dir string, cmdline string, keyval []string, verbose bool)\
cmd := exec.Command(v.cmd, args...)
cmd.Dir = dir
+ cmd.Env = envForDir(cmd.Dir)
if buildX {
fmt.Printf("cd %s\\n", dir)
fmt.Printf("%s %s\\n", v.cmd, strings.Join(args, " "))
vcsCmd.run1
関数内で、cmd.Env
にenvForDir(cmd.Dir)
を設定する行が追加されました。これにより、VCSコマンド実行時にもPWD
が設定されます。
コアとなるコードの解説
このコミットの最も重要な部分は、src/cmd/go/main.go
に追加された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=") { // 既存のPWDエントリを探す
env[i] = "PWD=" + dir // 見つかったら更新
return env
}
}
// Internally we only use rooted paths, so dir is rooted.
// Even if dir is not rooted, no harm done.
env = append(env, "PWD="+dir) // 見つからなかったら追加
return env
}
この関数は、Goコマンドが外部プロセスを起動する際に、そのプロセスに渡す環境変数リストを生成します。
os.Environ()
を呼び出すことで、現在のGoコマンド自身の環境変数リストのコピーを取得します。- ループを使って、このリストの中に
"PWD="
で始まるエントリがあるかどうかを確認します。 - もし
"PWD="
エントリが見つかった場合、そのエントリの値を、外部プロセスが実行されるべきディレクトリdir
のパスに更新します。そして、更新された環境変数リストをすぐに返します。これにより、既存のPWD
変数を上書きし、重複を防ぎます。 - もし
"PWD="
エントリが見つからなかった場合、新しい"PWD=" + dir
という形式のエントリを環境変数リストの末尾に追加します。 - 最終的に、
PWD
が適切に設定された環境変数リストが返されます。
このenvForDir
関数が、src/cmd/go/build.go
のbuilder.runOut
、src/cmd/go/test.go
のbuilder.runTest
、そしてsrc/cmd/go/vcs.go
のvcsCmd.run1
といった、外部コマンドを実行する主要な箇所でcmd.Env
フィールドに割り当てられることで、Goコマンドが起動するすべての外部プロセスが、その作業ディレクトリに対応する$PWD
環境変数を持つようになります。
これにより、外部プロセス内でos.Getwd
が呼び出された際に、多くのシステムでは$PWD
環境変数を参照して高速に現在の作業ディレクトリを取得できるようになり、システムコールによるオーバーヘッドが削減され、全体的なパフォーマンスが向上します。
関連リンク
- Go Code Review: https://golang.org/cl/7324055
参考にした情報源リンク
- https://github.com/golang/go/commit/357a18a2c625d06f0f9202142b3432458368484f
- Go言語の
os
パッケージドキュメント:os.Getwd
- Unix/Linuxの環境変数
PWD
に関する一般的な情報 - Go言語の
os/exec
パッケージドキュメント