[インデックス 19651] ファイルの概要
このコミットは、Go言語のNative Client (NaCl) 環境におけるファイルシステム (fs) の初期化を遅延させる(lazy initialization)ための変更を導入しています。これにより、Goプログラムの起動時パフォーマンスが向上し、特にファイルシステム操作が不要な場合のオーバーヘッドが削減されます。
コミット
commit bbe5c93e93922154d330cc4b7eecf148c830515c
Author: Shenghou Ma <minux@golang.org>
Date: Tue Jul 1 18:24:43 2014 -0400
misc/nacl, syscall: lazily initialize fs on nacl.
On amd64, the real time is reduced from 176.76s to 140.26s.
On ARM, the real time is reduced from 921.61s to 726.30s.
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/101580043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bbe5c93e93922154d330cc4b7eecf148c830515c
元コミット内容
このコミットは、GoのNative Client (NaCl) ターゲットにおいて、ファイルシステムの初期化を遅延実行するように変更します。これにより、amd64環境では実行時間が176.76秒から140.26秒に、ARM環境では921.61秒から726.30秒に短縮されるというパフォーマンス改善が報告されています。
変更の背景
GoプログラムがNative Client (NaCl) 環境で実行される際、ファイルシステムがプログラム起動時に常に初期化されていました。しかし、全てのGoプログラムがファイルシステムを必要とするわけではありません。ファイルシステムを必要としないプログラムの場合でも、その初期化処理が起動時間に不要なオーバーヘッドとして加算されていました。このコミットの目的は、ファイルシステムの初期化を、実際にファイルシステム操作が要求されるまで遅延させることで、Goプログラムの起動時間を短縮し、全体的なパフォーマンスを向上させることにあります。特に、NaCl環境のようなリソースが限られた、あるいは起動時間が重要な環境において、この最適化は大きな意味を持ちます。
前提知識の解説
Native Client (NaCl)
Native Client (NaCl) は、Googleが開発したサンドボックス技術で、ウェブブラウザ内でネイティブコード(C/C++など)を安全に実行することを可能にします。Go言語もNaClをターゲットとしてサポートしており、GoプログラムをNaClモジュールとしてコンパイルし、ウェブアプリケーション内で実行することができます。NaCl環境は、通常のオペレーティングシステムとは異なる独自のサンドボックス化されたファイルシステムやシステムコールインターフェースを持っています。
遅延初期化 (Lazy Initialization)
遅延初期化とは、オブジェクトやリソースの初期化を、それが実際に必要とされる時点まで遅らせる設計パターンです。これにより、プログラムの起動時や、特定の機能が全く使用されない場合に、不要なリソースの割り当てや計算を避けることができます。結果として、起動時間の短縮、メモリ使用量の削減、パフォーマンスの向上が期待できます。
Go言語の init()
関数
Go言語では、各パッケージにinit()
関数を定義することができます。init()
関数は、パッケージがインポートされた際に、main()
関数が実行されるよりも前に自動的に実行されます。複数のinit()
関数がある場合、それらは定義された順序で実行されます。通常、init()
関数は、プログラムの起動時に必要な初期設定やリソースの準備に使用されます。
sync.Once
Go言語のsync
パッケージに含まれるOnce
型は、特定の処理が一度だけ実行されることを保証するためのメカニズムです。Once.Do(f func())
メソッドを呼び出すと、引数として渡された関数f
が、Do
が初めて呼び出されたときに一度だけ実行されます。その後のDo
の呼び出しでは、f
は実行されません。これは、複数のゴルーチンから同時に呼び出された場合でもスレッドセーフに一度だけ実行されることを保証するため、遅延初期化のパターンで非常に有用です。
Go言語の syscall
パッケージ
syscall
パッケージは、Goプログラムが基盤となるオペレーティングシステムのシステムコールに直接アクセスするためのインターフェースを提供します。ファイル操作、プロセス管理、ネットワーク通信など、OSレベルの機能を利用する際に使用されます。NaCl環境では、このパッケージを通じてNaCl独自のシステムコールインターフェースとやり取りします。
技術的詳細
このコミットの核心は、GoのNaCl環境におけるファイルシステム初期化のタイミングを、プログラム起動時(init()
関数内)から、実際にファイルシステム操作が要求される時点まで遅延させることです。
具体的には、以下のメカニズムが導入されています。
fsinit
関数の導入:src/pkg/syscall/fs_nacl.go
にvar fsinit = func() {}
というグローバル変数が導入されました。この関数は、ファイルシステムの実際の初期化ロジックをカプセル化します。mkzip.go
によるfsinit
の設定:misc/nacl/mkzip.go
は、NaCl向けにGoプログラムをパッケージ化する際に、fsinit
関数を生成されるGoパッケージのinit()
関数内で設定するように変更されました。この設定では、sync.Once
を使用して、fsinit
が呼び出された際に、内部のファイルシステム初期化処理(unzip
関数によるファイルシステムデータの展開など)が一度だけ実行されるように保証します。- システムコールからの
fsinit
呼び出し:src/pkg/syscall/fs_nacl.go
内のOpen
,Stat
,unlink
,Chmod
,Chown
,UtimesNano
,Link
,Rename
,Truncate
,Chdir
といった主要なファイルシステム関連のシステムコール関数が、処理の開始時にfsinit()
を呼び出すように変更されました。これにより、これらの関数が初めて呼び出されたときにのみ、ファイルシステムの初期化が実行されます。 init()
関数内の保護:src/pkg/syscall/fs_nacl.go
のinit()
関数自体も変更され、一時的にfsinit
を空の関数に設定し、defer
を使って元のfsinit
に戻すという処理が追加されました。これは、init()
関数内で実行される他の初期化処理(例:/dev
や/tmp
ディレクトリの作成)が、意図せずファイルシステムの初期化をトリガーしてしまうことを防ぐための安全策です。
この変更により、ファイルシステムを全く使用しないGoプログラムは、ファイルシステムの初期化にかかる時間を完全にスキップできるようになり、起動時間が大幅に短縮されます。ファイルシステムを使用するプログラムであっても、初期化は最初のファイルシステム操作時に一度だけ行われるため、全体的なパフォーマンスへの影響は最小限に抑えられます。
コアとなるコードの変更箇所
misc/nacl/mkzip.go
mkzip.go
は、GoのNaClバイナリをパッケージ化する際に、ファイルシステム初期化ロジックを生成されるGoコードに埋め込む役割を担っています。
--- a/misc/nacl/mkzip.go
+++ b/misc/nacl/mkzip.go
@@ -71,7 +71,13 @@ func main() {
var w io.Writer = zf
if *gopackage != "" {
- fmt.Fprintf(zf, "package %s\\n\\nfunc init() {\\n\\tunzip(\\\"\", *gopackage)\n
+ fmt.Fprintf(zf, `package %s
+import "sync"
+func init() {
+ var once sync.Once
+ fsinit = func() {
+ once.Do(func() {
+ unzip("`, *gopackage)
gw := &goWriter{b: bufio.NewWriter(w)}\
defer func() {
if err := gw.Close(); err != nil {
@@ -214,7 +220,7 @@ func (w *goWriter) Write(b []byte) (int, error) {
}
func (w *goWriter) Close() error {
- fmt.Fprintf(w.b, "\\\")\\n}\\n")
+ fmt.Fprintf(w.b, "\\\")\\n\\t\\t})\\n\\t}\\n}\")
w.b.Flush()
return nil
}
この変更により、生成されるGoパッケージのinit()
関数は、sync
パッケージをインポートし、sync.Once
インスタンスonce
を宣言します。そして、fsinit
グローバル変数に、once.Do
を使ってunzip
関数を呼び出す匿名関数を代入します。これにより、unzip
はfsinit
が初めて呼び出されたときに一度だけ実行されるようになります。
src/pkg/syscall/fs_nacl.go
fs_nacl.go
は、NaCl環境におけるファイルシステム関連のシステムコールを実装しています。
--- a/src/pkg/syscall/fs_nacl.go
+++ b/src/pkg/syscall/fs_nacl.go
@@ -79,8 +79,13 @@ func newFsys() *fsys {
}
var fs = newFsys()
+var fsinit = func() {}\n
func init() {
+\t// do not trigger loading of zipped file system here
+\toldFsinit := fsinit
+\tdefer func() { fsinit = oldFsinit }()
+\tfsinit = func() {}\n
Mkdir("/dev", 0555)
Mkdir("/tmp\", 0777)
mkdev("/dev/null", 0666, openNull)
@@ -93,7 +98,7 @@ func chdirEnv() {
\tpwd, ok := Getenv(\"NACLPWD\")
\tif ok {
-\t\tChdir(pwd)
+\t\tchdir(pwd)
\t}\n
}\n
@@ -465,6 +470,7 @@ func (f *fsysFile) pwriteLocked(b []byte, offset int64) (int, error) {
// Standard Unix system calls.\n
func Open(path string, openmode int, perm uint32) (fd int, err error) {
+\tfsinit()\n
\tfs.mu.Lock()
\tdefer fs.mu.Unlock()
\tf, err := fs.open(path, openmode, perm&0777|S_IFREG)\n
@@ -487,6 +493,7 @@ func Getcwd(buf []byte) (n int, err error) {
}\n
func Stat(path string, st *Stat_t) error {
+\tfsinit()\n
\tfs.mu.Lock()
\tdefer fs.mu.Unlock()
\tip, _, err := fs.namei(path, false)\n
@@ -502,6 +509,7 @@ func Lstat(path string, st *Stat_t) error {
}\n
func unlink(path string, isdir bool) error {
+\tfsinit()\n
\tfs.mu.Lock()
\tdefer fs.mu.Unlock()
\tdp, elem, err := fs.namei(path, true)\n
@@ -543,6 +551,7 @@ func Rmdir(path string) error {
}\n
func Chmod(path string, mode uint32) error {
+\tfsinit()\n
\tfs.mu.Lock()
\tdefer fs.mu.Unlock()
\tip, _, err := fs.namei(path, false)\n
@@ -565,6 +574,7 @@ func Fchmod(fd int, mode uint32) error {
}\n
func Chown(path string, uid, gid int) error {
+\tfsinit()\n
\tfs.mu.Lock()
\tdefer fs.mu.Unlock()
\tip, _, err := fs.namei(path, false)\n
@@ -598,6 +608,7 @@ func UtimesNano(path string, ts []Timespec) error {
\tif len(ts) != 2 {\n
\t\treturn EINVAL\n
\t}\n+\tfsinit()\n
\tfs.mu.Lock()
\tdefer fs.mu.Unlock()
\tip, _, err := fs.namei(path, false)\n
@@ -612,6 +623,7 @@ func UtimesNano(path string, ts []Timespec) error {
}\n
func Link(path, link string) error {
+\tfsinit()\n
\tip, _, err := fs.namei(path, false)\n
\tif err != nil {\n
\t\treturn err\n
@@ -628,6 +640,7 @@ func Link(path, link string) error {
}\n
func Rename(from, to string) error {
+\tfsinit()\n
\tfdp, felem, err := fs.namei(from, true)\n
\tif err != nil {\n
\t\treturn err\n
@@ -664,6 +677,7 @@ func (fs *fsys) truncate(ip *inode, length int64) error {\n }\n
func Truncate(path string, length int64) error {
+\tfsinit()\n
\tfs.mu.Lock()
\tdefer fs.mu.Unlock()
\tip, _, err := fs.namei(path, false)\n
@@ -684,6 +698,11 @@ func Ftruncate(fd int, length int64) error {
}\n
func Chdir(path string) error {
+\tfsinit()\n
+\treturn chdir(path)\n
+}\n+\n+func chdir(path string) error {\n
\tfs.mu.Lock()
\tdefer fs.mu.Unlock()
\tip, _, err := fs.namei(path, false)\n
@@ -723,8 +742,6 @@ func Fsync(fd int) error {
// Special devices.\n
func mkdev(path string, mode uint32, open func() (devFile, error)) error {
-\tfs.mu.Lock()\n
-\tfs.mu.Unlock()\n
\tf, err := fs.open(path, O_CREATE|O_RDONLY|O_EXCL, S_IFCHR|mode)\n
\tif err != nil {\n
\t\treturn err\n
このファイルでは、fsinit
グローバル変数の宣言、init()
関数でのfsinit
の一時的な無効化と復元、そしてOpen
, Stat
などの多くのファイルシステム関連関数でのfsinit()
の呼び出しが追加されています。また、Chdir
関数がfsinit()
を呼び出す新しいchdir
関数に処理を委譲するように変更され、mkdev
関数から不要なロックが削除されています。
コアとなるコードの解説
このコミットの主要な変更は、GoのNaCl環境におけるファイルシステムの初期化ロジックを、init()
関数からfsinit
という新しいグローバル関数に移動し、そのfsinit
関数がsync.Once
によって一度だけ実行されるように制御することです。
-
fsinit
の導入とsync.Once
による制御:src/pkg/syscall/fs_nacl.go
にvar fsinit = func() {}
が追加されました。これは、ファイルシステムの初期化処理をカプセル化するための関数ポインタです。misc/nacl/mkzip.go
で生成されるGoコードのinit()
関数内で、このfsinit
に実際の初期化ロジックが設定されます。具体的には、sync.Once
のインスタンスonce
が作成され、fsinit
はonce.Do(func() { unzip(...) })
を呼び出す関数として定義されます。これにより、fsinit
が複数回呼び出されても、unzip
(ファイルシステムデータの展開)は一度だけ実行されることが保証されます。
-
システムコールからの遅延初期化のトリガー:
src/pkg/syscall/fs_nacl.go
内のOpen
,Stat
,unlink
,Chmod
など、ファイルシステムにアクセスするほぼ全てのシステムコール関数が、その処理の冒頭でfsinit()
を呼び出すように変更されました。- これにより、Goプログラムがファイルシステム操作を初めて実行する際に、
fsinit()
が呼び出され、sync.Once
のメカニズムによってファイルシステムの初期化が実行されます。一度初期化が完了すれば、それ以降のfsinit()
の呼び出しは何も行いません。
-
init()
関数内の保護メカニズム:src/pkg/syscall/fs_nacl.go
のinit()
関数自体も変更されました。このinit()
関数は、/dev
や/tmp
などの基本的なディレクトリを作成しますが、これらの操作が間接的にfsinit
を呼び出してしまう可能性を考慮し、一時的にfsinit
を空の関数に設定し、defer
を使って元のfsinit
に戻すという処理が追加されました。これは、init()
関数が実行されている間は、ファイルシステムの完全な初期化が意図せずトリガーされないようにするための防御的なプログラミングです。
-
Chdir
関数のリファクタリング:Chdir
関数は、fsinit()
を呼び出した後、実際のディレクトリ変更ロジックを新しい内部関数chdir
に委譲するように変更されました。これは、他のシステムコール関数と同様に、Chdir
が呼び出されたときにファイルシステムの初期化が確実に行われるようにするためです。
これらの変更により、GoのNaClプログラムは、ファイルシステムを必要としない限り、その初期化コストを完全に回避できるようになり、起動時間の短縮という明確なパフォーマンス上の利点が得られました。
関連リンク
- Go Native Client (NaCl) の公式ドキュメント (当時の情報に基づく)
- Go言語の
sync
パッケージに関するドキュメント - Go言語の
init
関数に関する公式ドキュメント
参考にした情報源リンク
- Go言語の
sync.Once
の使い方 - Go言語の
init
関数 - Google Native Client (NaCl) の概要 (当時の情報に基づく、現在は非推奨)
- golang/go リポジトリ
- Gerrit Change 101580043 (コミットメッセージに記載されたGerritの変更リンク)
- Go言語の
syscall
パッケージ
[インデックス 19651] ファイルの概要
このコミットは、Go言語のNative Client (NaCl) 環境におけるファイルシステム (fs) の初期化を遅延させる(lazy initialization)ための変更を導入しています。これにより、Goプログラムの起動時パフォーマンスが向上し、特にファイルシステム操作が不要な場合のオーバーヘッドが削減されます。
コミット
commit bbe5c93e93922154d330cc4b7eecf148c830515c
Author: Shenghou Ma <minux@golang.org>
Date: Tue Jul 1 18:24:43 2014 -0400
misc/nacl, syscall: lazily initialize fs on nacl.
On amd64, the real time is reduced from 176.76s to 140.26s.
On ARM, the real time is reduced from 921.61s to 726.30s.
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/101580043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bbe5c93e93922154d330cc4b7eecf148c830515c
元コミット内容
このコミットは、GoのNative Client (NaCl) ターゲットにおいて、ファイルシステムの初期化を遅延実行するように変更します。これにより、amd64環境では実行時間が176.76秒から140.26秒に、ARM環境では921.61秒から726.30秒に短縮されるというパフォーマンス改善が報告されています。
変更の背景
GoプログラムがNative Client (NaCl) 環境で実行される際、ファイルシステムがプログラム起動時に常に初期化されていました。しかし、全てのGoプログラムがファイルシステムを必要とするわけではありません。ファイルシステムを必要としないプログラムの場合でも、その初期化処理が起動時間に不要なオーバーヘッドとして加算されていました。このコミットの目的は、ファイルシステムの初期化を、実際にファイルシステム操作が要求されるまで遅延させることで、Goプログラムの起動時間を短縮し、全体的なパフォーマンスを向上させることにあります。特に、NaCl環境のようなリソースが限られた、あるいは起動時間が重要な環境において、この最適化は大きな意味を持ちます。
前提知識の解説
Native Client (NaCl)
Native Client (NaCl) は、Googleが開発したサンドボックス技術で、ウェブブラウザ内でネイティブコード(C/C++など)を安全に実行することを可能にします。Go言語もNaClをターゲットとしてサポートしており、GoプログラムをNaClモジュールとしてコンパイルし、ウェブアプリケーション内で実行することができます。NaCl環境は、通常のオペレーティングシステムとは異なる独自のサンドボックス化されたファイルシステムやシステムコールインターフェースを持っています。
遅延初期化 (Lazy Initialization)
遅延初期化とは、オブジェクトやリソースの初期化を、それが実際に必要とされる時点まで遅らせる設計パターンです。これにより、プログラムの起動時や、特定の機能が全く使用されない場合に、不要なリソースの割り当てや計算を避けることができます。結果として、起動時間の短縮、メモリ使用量の削減、パフォーマンスの向上が期待できます。
Go言語の init()
関数
Go言語では、各パッケージにinit()
関数を定義することができます。init()
関数は、パッケージがインポートされた際に、main()
関数が実行されるよりも前に自動的に実行されます。複数のinit()
関数がある場合、それらは定義された順序で実行されます。通常、init()
関数は、プログラムの起動時に必要な初期設定やリソースの準備に使用されます。
sync.Once
Go言語のsync
パッケージに含まれるOnce
型は、特定の処理が一度だけ実行されることを保証するためのメカニズムです。Once.Do(f func())
メソッドを呼び出すと、引数として渡された関数f
が、Do
が初めて呼び出されたときに一度だけ実行されます。その後のDo
の呼び出しでは、f
は実行されません。これは、複数のゴルーチンから同時に呼び出された場合でもスレッドセーフに一度だけ実行されることを保証するため、遅延初期化のパターンで非常に有用です。
Go言語の syscall
パッケージ
syscall
パッケージは、Goプログラムが基盤となるオペレーティングシステムのシステムコールに直接アクセスするためのインターフェースを提供します。ファイル操作、プロセス管理、ネットワーク通信など、OSレベルの機能を利用する際に使用されます。NaCl環境では、このパッケージを通じてNaCl独自のシステムコールインターフェースとやり取りします。
技術的詳細
このコミットの核心は、GoのNaCl環境におけるファイルシステム初期化のタイミングを、プログラム起動時(init()
関数内)から、実際にファイルシステム操作が要求される時点まで遅延させることです。
具体的には、以下のメカニズムが導入されています。
fsinit
関数の導入:src/pkg/syscall/fs_nacl.go
にvar fsinit = func() {}
というグローバル変数が導入されました。この関数は、ファイルシステムの実際の初期化ロジックをカプセル化します。mkzip.go
によるfsinit
の設定:misc/nacl/mkzip.go
は、NaCl向けにGoプログラムをパッケージ化する際に、fsinit
関数を生成されるGoパッケージのinit()
関数内で設定するように変更されました。この設定では、sync.Once
を使用して、fsinit
が呼び出された際に、内部のファイルシステム初期化処理(unzip
関数によるファイルシステムデータの展開など)が一度だけ実行されるように保証します。- システムコールからの
fsinit
呼び出し:src/pkg/syscall/fs_nacl.go
内のOpen
,Stat
,unlink
,Chmod
,Chown
,UtimesNano
,Link
,Rename
,Truncate
,Chdir
といった主要なファイルシステム関連のシステムコール関数が、処理の開始時にfsinit()
を呼び出すように変更されました。これにより、これらの関数が初めて呼び出されたときにのみ、ファイルシステムの初期化が実行されます。 init()
関数内の保護:src/pkg/syscall/fs_nacl.go
のinit()
関数自体も変更され、一時的にfsinit
を空の関数に設定し、defer
を使って元のfsinit
に戻すという処理が追加されました。これは、init()
関数内で実行される他の初期化処理(例:/dev
や/tmp
ディレクトリの作成)が、意図せずファイルシステムの初期化をトリガーしてしまうことを防ぐための安全策です。
この変更により、ファイルシステムを全く使用しないGoプログラムは、ファイルシステムの初期化にかかる時間を完全にスキップできるようになり、起動時間が大幅に短縮されます。ファイルシステムを使用するプログラムであっても、初期化は最初のファイルシステム操作時に一度だけ行われるため、全体的なパフォーマンスへの影響は最小限に抑えられます。
コアとなるコードの変更箇所
misc/nacl/mkzip.go
mkzip.go
は、GoのNaClバイナリをパッケージ化する際に、ファイルシステム初期化ロジックを生成されるGoコードに埋め込む役割を担っています。
--- a/misc/nacl/mkzip.go
+++ b/misc/nacl/mkzip.go
@@ -71,7 +71,13 @@ func main() {
var w io.Writer = zf
if *gopackage != "" {
- fmt.Fprintf(zf, "package %s\\n\\nfunc init() {\\n\\tunzip(\\\"\", *gopackage)\n
+ fmt.Fprintf(zf, `package %s
+import "sync"
+func init() {
+ var once sync.Once
+ fsinit = func() {
+ once.Do(func() {
+ unzip("`, *gopackage)
gw := &goWriter{b: bufio.NewWriter(w)}\
defer func() {
if err := gw.Close(); err != nil {
@@ -214,7 +220,7 @@ func (w *goWriter) Write(b []byte) (int, error) {
}
func (w *goWriter) Close() error {
- fmt.Fprintf(w.b, "\\\")\\n}\\n")
+ fmt.Fprintf(w.b, "\\\")\\n\\t\\t})\\n\\t}\\n}\")
w.b.Flush()
return nil
}
この変更により、生成されるGoパッケージのinit()
関数は、sync
パッケージをインポートし、sync.Once
インスタンスonce
を宣言します。そして、fsinit
グローバル変数に、once.Do
を使ってunzip
関数を呼び出す匿名関数を代入します。これにより、unzip
はfsinit
が初めて呼び出されたときに一度だけ実行されるようになります。
src/pkg/syscall/fs_nacl.go
fs_nacl.go
は、NaCl環境におけるファイルシステム関連のシステムコールを実装しています。
--- a/src/pkg/syscall/fs_nacl.go
+++ b/src/pkg/syscall/fs_nacl.go
@@ -79,8 +79,13 @@ func newFsys() *fsys {
}
var fs = newFsys()
+var fsinit = func() {}\n
func init() {
+\t// do not trigger loading of zipped file system here
+\toldFsinit := fsinit
+\tdefer func() { fsinit = oldFsinit }()
+\tfsinit = func() {}\n
Mkdir("/dev", 0555)
Mkdir("/tmp\", 0777)
mkdev("/dev/null", 0666, openNull)
@@ -93,7 +98,7 @@ func chdirEnv() {
\tpwd, ok := Getenv(\"NACLPWD\")
\tif ok {
-\t\tChdir(pwd)
+\t\tchdir(pwd)
\t}\n
}\n
@@ -465,6 +470,7 @@ func (f *fsysFile) pwriteLocked(b []byte, offset int64) (int, error) {
// Standard Unix system calls.\n
func Open(path string, openmode int, perm uint32) (fd int, err error) {
+\tfsinit()\n
\tfs.mu.Lock()
\tdefer fs.mu.Unlock()
\tf, err := fs.open(path, openmode, perm&0777|S_IFREG)\n
@@ -487,6 +493,7 @@ func Getcwd(buf []byte) (n int, err error) {
}\n
func Stat(path string, st *Stat_t) error {
+\tfsinit()\n
\tfs.mu.Lock()
\tdefer fs.mu.Unlock()
\tip, _, err := fs.namei(path, false)\n
@@ -502,6 +509,7 @@ func Lstat(path string, st *Stat_t) error {
}\n
func unlink(path string, isdir bool) error {
+\tfsinit()\n
\tfs.mu.Lock()
\tdefer fs.mu.Unlock()
\tdp, elem, err := fs.namei(path, true)\n
@@ -543,6 +551,7 @@ func Rmdir(path string) error {
}\n
func Chmod(path string, mode uint32) error {
+\tfsinit()\n
\tfs.mu.Lock()
\tdefer fs.mu.Unlock()
\tip, _, err := fs.namei(path, false)\n
@@ -565,6 +574,7 @@ func Fchmod(fd int, mode uint32) error {
}\n
func Chown(path string, uid, gid int) error {
+\tfsinit()\n
\tfs.mu.Lock()
\tdefer fs.mu.Unlock()
\tip, _, err := fs.namei(path, false)\n
@@ -598,6 +608,7 @@ func UtimesNano(path string, ts []Timespec) error {
\tif len(ts) != 2 {\n
\t\treturn EINVAL\n
\t}\n+\tfsinit()\n
\tfs.mu.Lock()
\tdefer fs.mu.Unlock()
\tip, _, err := fs.namei(path, false)\n
@@ -612,6 +623,7 @@ func UtimesNano(path string, ts []Timespec) error {
}\n
func Link(path, link string) error {
+\tfsinit()\n
\tip, _, err := fs.namei(path, false)\n
\tif err != nil {\n
\t\treturn err\n
@@ -628,6 +640,7 @@ func Link(path, link string) error {
}\n
func Rename(from, to string) error {
+\tfsinit()\n
\tfdp, felem, err := fs.namei(from, true)\n
\tif err != nil {\n
\t\treturn err\n
@@ -664,6 +677,7 @@ func (fs *fsys) truncate(ip *inode, length int64) error {\n }\n
func Truncate(path string, length int64) error {
+\tfsinit()\n
\tfs.mu.Lock()
\tdefer fs.mu.Unlock()
\tip, _, err := fs.namei(path, false)\n
@@ -684,6 +698,11 @@ func Ftruncate(fd int, length int64) error {
}\n
func Chdir(path string) error {
+\tfsinit()\n
+\treturn chdir(path)\n
+}\n+\n+func chdir(path string) error {\n
\tfs.mu.Lock()
\tdefer fs.mu.Unlock()
\tip, _, err := fs.namei(path, false)\n
@@ -723,8 +742,6 @@ func Fsync(fd int) error {
// Special devices.\n
func mkdev(path string, mode uint32, open func() (devFile, error)) error {
-\tfs.mu.Lock()\n
-\tfs.mu.Unlock()\n
\tf, err := fs.open(path, O_CREATE|O_RDONLY|O_EXCL, S_IFCHR|mode)\n
\tif err != nil {\n
\t\treturn err\n
このファイルでは、fsinit
グローバル変数の宣言、init()
関数でのfsinit
の一時的な無効化と復元、そしてOpen
, Stat
などの多くのファイルシステム関連関数でのfsinit()
の呼び出しが追加されています。また、Chdir
関数がfsinit()
を呼び出す新しいchdir
関数に処理を委譲するように変更され、mkdev
関数から不要なロックが削除されています。
コアとなるコードの解説
このコミットの主要な変更は、GoのNaCl環境におけるファイルシステムの初期化ロジックを、init()
関数からfsinit
という新しいグローバル関数に移動し、そのfsinit
関数がsync.Once
によって一度だけ実行されるように制御することです。
-
fsinit
の導入とsync.Once
による制御:src/pkg/syscall/fs_nacl.go
にvar fsinit = func() {}
が追加されました。これは、ファイルシステムの初期化処理をカプセル化するための関数ポインタです。misc/nacl/mkzip.go
で生成されるGoコードのinit()
関数内で、このfsinit
に実際の初期化ロジックが設定されます。具体的には、sync.Once
のインスタンスonce
が作成され、fsinit
はonce.Do(func() { unzip(...) })
を呼び出す関数として定義されます。これにより、fsinit
が複数回呼び出されても、unzip
(ファイルシステムデータの展開)は一度だけ実行されることが保証されます。
-
システムコールからの遅延初期化のトリガー:
src/pkg/syscall/fs_nacl.go
内のOpen
,Stat
,unlink
,Chmod
など、ファイルシステムにアクセスするほぼ全てのシステムコール関数が、その処理の冒頭でfsinit()
を呼び出すように変更されました。- これにより、Goプログラムがファイルシステム操作を初めて実行する際に、
fsinit()
が呼び出され、sync.Once
のメカニズムによってファイルシステムの初期化が実行されます。一度初期化が完了すれば、それ以降のfsinit()
の呼び出しは何も行いません。
-
init()
関数内の保護メカニズム:src/pkg/syscall/fs_nacl.go
のinit()
関数自体も変更されました。このinit()
関数は、/dev
や/tmp
などの基本的なディレクトリを作成しますが、これらの操作が間接的にfsinit
を呼び出してしまう可能性を考慮し、一時的にfsinit
を空の関数に設定し、defer
を使って元のfsinit
に戻すという処理が追加されました。これは、init()
関数が実行されている間は、ファイルシステムの完全な初期化が意図せずトリガーされないようにするための防御的なプログラミングです。
-
Chdir
関数のリファクタリング:Chdir
関数は、fsinit()
を呼び出した後、実際のディレクトリ変更ロジックを新しい内部関数chdir
に委譲するように変更されました。これは、他のシステムコール関数と同様に、Chdir
が呼び出されたときにファイルシステムの初期化が確実に行われるようにするためです。
これらの変更により、GoのNaClプログラムは、ファイルシステムを必要としない限り、その初期化コストを完全に回避できるようになり、起動時間の短縮という明確なパフォーマンス上の利点が得られました。
関連リンク
- Go Native Client (NaCl) の公式ドキュメント (当時の情報に基づく)
- Go言語の
sync
パッケージに関するドキュメント - Go言語の
init
関数に関する公式ドキュメント
参考にした情報源リンク
- Go言語の
sync.Once
の使い方 - Go言語の
init
関数 - Google Native Client (NaCl) の概要 (当時の情報に基づく、現在は非推奨)
- golang/go リポジトリ
- Gerrit Change 101580043 (コミットメッセージに記載されたGerritの変更リンク)
- Go言語の
syscall
パッケージ