[インデックス 19695] ファイルの概要
このコミットは、GoランタイムがPlan 9オペレーティングシステム上で時刻を取得する方法を変更するものです。具体的には、従来の/dev/bintime
デバイスファイルからの読み取りに代わり、nsec
システムコールを使用するように修正されています。これにより、時刻取得の効率性と正確性が向上します。
コミット
- コミットハッシュ:
a84e3ad198387019aaef6e979e46e498600ea12f
- 作者: Aram Hăvărneanu aram@mgk.ro
- コミット日時: 2014年7月9日 水曜日 12:33:42 +0200
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a84e3ad198387019aaef6e979e46e498600ea12f
元コミット内容
runtime: use the nsec system call instead of /dev/bintime on Plan 9
LGTM=0intro
R=0intro
CC=ality, dave, golang-codereviews, jas, mischief, rsc
https://golang.org/cl/104570043
変更の背景
この変更の背景には、Plan 9オペレーティングシステムにおける時刻取得メカニズムの進化と、Goランタイムのパフォーマンスおよび堅牢性の向上が挙げられます。
従来のPlan 9では、/dev/bintime
というデバイスファイルを通じてバイナリ形式の時刻情報を取得していました。Goランタイムのruntime·nanotime
関数も、このファイルを開いて読み取ることでナノ秒単位の時刻を取得していました。しかし、このファイルベースのアプローチにはいくつかの課題がありました。特に、プロセスがフォーク(複製)される際にファイルディスクリプタの管理が複雑になり、エラーが発生しやすくなるという問題がありました。元のコードのコメントにも「As long as all goroutines share the same file descriptor table we can get away with using just a static fd. Without a lock the file can be opened twice but that's okay.」とあり、静的なファイルディスクリプタを使用する上での考慮事項が示されています。また、パフォーマンス面でも、/dev/bintime
からの読み取りは、特に頻繁に時刻を取得するようなシナリオにおいて、オーバーヘッドが大きくなる可能性がありました。元のコメントでは「Using /dev/bintime gives us a latency on the order of ten microseconds between two calls.」とあり、さらに「The naïve implementation (without the cached file descriptor) is roughly four times slower in 9vx on a 2.16 GHz Intel Core 2 Duo.」と、パフォーマンスの懸念が具体的に示されています。
これに対し、nsec
システムコールは、より直接的かつ効率的にナノ秒単位の時刻情報を提供する現代的な方法です。システムコールとして実装されているため、ファイルI/Oのオーバーヘッドを回避でき、ファイルディスクリプタ管理の複雑さも解消されます。この変更は、GoランタイムがPlan 9上でより高速かつ安定して動作するために不可欠でした。
また、os_plan9.c
内のruntime·nanotime
関数には// TODO(aram): remove hack after I fix _nsec in the pc64 kernel.
というコメントがあり、これはnsec
システムコール自体が当時のpc64カーネルでまだ完全に最適化されていなかった可能性を示唆しています。このコミットは、その時点での最善の解決策としてnsec
システムコールへの移行を選択しつつ、将来的なカーネル側の改善を見越していたと考えられます。
前提知識の解説
Plan 9オペレーティングシステム
Plan 9 from Bell Labsは、ベル研究所で開発された分散オペレーティングシステムです。その設計哲学は「すべてがファイルである」という原則に基づいています。これは、システム内のあらゆるリソース(プロセス、ネットワーク接続、デバイスなど)がファイルシステム上のファイルとして表現され、標準的なファイルI/O操作(読み書き、オープン、クローズなど)を通じてアクセスできることを意味します。これにより、システム管理とプログラミングが簡素化されることを目指しています。
/dev/bintime
Plan 9における/dev/bintime
は、システム時刻をバイナリ形式で提供する特殊なデバイスファイルです。プログラムはこのファイルを開き、その内容を読み取ることで、現在の時刻情報を取得していました。これは「すべてがファイルである」というPlan 9の哲学を体現する一例ですが、前述の通り、ファイルディスクリプタの管理やパフォーマンスの面で課題がありました。
nsec
システムコール
nsec
は、Plan 9オペレーティングシステムが提供するシステムコールの一つで、Unixエポック(1970年1月1日00:00:00 UTC)からの経過時間をナノ秒単位で返します。システムコールは、ユーザー空間のプログラムがカーネル空間の機能に直接アクセスするためのメカニズムであり、ファイルI/Oを介するよりも低オーバーヘッドで高速な処理が可能です。nsec
システムコールは、より高精度かつ効率的な時刻取得のために導入されました。
Goランタイム
Goランタイムは、Go言語で書かれたプログラムの実行を管理するソフトウェアコンポーネントです。これには、ガベージコレクション、スケジューリング、メモリ管理、そしてシステムコールへのインターフェースなどが含まれます。runtime
パッケージは、Goプログラムがオペレーティングシステムと対話するための低レベルな機能を提供し、runtime·nanotime
のような関数は、OS固有の時刻取得メカニズムを抽象化してGoプログラムに提供します。
アセンブリ言語 (386/amd64)
アセンブリ言語は、コンピュータのプロセッサが直接実行できる機械語に非常に近い低レベルのプログラミング言語です。386(Intel 80386)およびamd64(x86-64)は、それぞれ32ビットおよび64ビットのx86アーキテクチャを指します。オペレーティングシステムのカーネルやランタイムのような低レベルのソフトウェアでは、特定のシステムコールを呼び出すためや、パフォーマンスが非常に重要な部分で、アセンブリ言語が直接使用されることがあります。このコミットでは、nsec
システムコールを呼び出すためのアセンブリコードが追加されています。
技術的詳細
このコミットの主要な技術的変更は、Goランタイムのruntime·nanotime
関数が、Plan 9上で時刻を取得するメカニズムを/dev/bintime
からのファイル読み取りからnsec
システムコールへの直接呼び出しに切り替えた点です。
変更前 (/dev/bintime
方式):
変更前のruntime·nanotime
関数は、静的なファイルディスクリプタfd
を使用して/dev/bintime
を開き、そこから8バイトのバイナリデータを読み取っていました。この8バイトは、上位4バイトが時刻の「上位部分」、下位4バイトが「下位部分」を表し、これらを組み合わせて64ビットのナノ秒単位の時刻を構築していました。
この方式の課題は、ファイルI/Oのオーバーヘッドと、ファイルディスクリプタの管理(特にマルチスレッド環境やフォークを伴うプロセスにおいて)の複雑さにありました。コメントにもあるように、2回の呼び出し間で約10マイクロ秒のレイテンシがあり、キャッシュされていないファイルディスクリプタを使用するとパフォーマンスが著しく低下する可能性がありました。
変更後 (nsec
システムコール方式):
変更後のruntime·nanotime
関数は、新たに導入されたruntime·nsec
関数を呼び出すようになりました。runtime·nsec
は、Plan 9のnsec
システムコールを直接呼び出すためのGoランタイムのラッパー関数です。このシステムコールは、ナノ秒単位の時刻を直接返します。
この変更により、ファイルI/Oのオーバーヘッドが排除され、時刻取得のレイテンシが大幅に削減されます。また、ファイルディスクリプタの管理に関する複雑さも解消されます。
runtime·nsec
の実装:
runtime·nsec
関数は、src/pkg/runtime/os_plan9.h
で宣言され、src/pkg/runtime/sys_plan9_386.s
とsrc/pkg/runtime/sys_plan9_amd64.s
にそれぞれ32ビット(x86)および64ビット(x86-64)アーキテクチャ向けのアセンブリ実装が追加されました。
-
sys_plan9_386.s
(32ビット):TEXT runtime·nsec(SB),NOSPLIT,$0
で関数が定義され、MOVL $53, AX
でシステムコール番号53(nsec
システムコールに対応)をAX
レジスタにロードし、INT $64
でシステムコールを呼び出しています。これはPlan 9におけるシステムコール呼び出しの一般的なパターンです。返された値はAX
レジスタに格納され、それが関数の戻り値となります。また、CMPL AX, $-1
でエラーチェックを行い、エラーの場合はscratch
引数にエラーコードを格納する処理も含まれています。 -
sys_plan9_amd64.s
(64ビット):TEXT runtime·nsec(SB),NOSPLIT,$0
で関数が定義され、MOVQ $53, BP
でシステムコール番号53をBP
レジスタにロードし、SYSCALL
命令でシステムコールを呼び出しています。64ビットアーキテクチャでは、システムコール呼び出しにSYSCALL
命令が使用されるのが一般的です。
一時的なハック (TODO
コメント):
os_plan9.c
のruntime·nanotime
関数には、// TODO(aram): remove hack after I fix _nsec in the pc64 kernel.
というコメントがあります。これは、nsec
システムコールがpc64カーネルでまだ完全に機能していない、または特定の条件下で0
を返す可能性があるため、一時的にscratch
引数に格納された値を使用するという回避策が取られていることを示しています。これは、このコミットがnsec
システムコールへの移行を促進しつつも、当時のPlan 9カーネルの特定の制約に対応していたことを示唆しています。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードブロックは以下の通りです。
-
src/pkg/runtime/os_plan9.c
:runtime·nanotime
関数の実装が完全に書き換えられました。/dev/bintime
を開くためのfd
変数、ファイル読み取り、バイト操作のロジックが削除されました。- 代わりに、
runtime·nsec(&scratch)
を呼び出し、その戻り値を使用するようになりました。 - 一時的なハックとして、
ns == 0
の場合にscratch
の値を返すロジックが追加されました。
-
src/pkg/runtime/os_plan9.h
:runtime·nsec
関数のプロトタイプ宣言が追加されました:int64 runtime·nsec(int64*);
-
src/pkg/runtime/sys_plan9_386.s
:runtime·nsec
という新しいアセンブリ関数が追加されました。- この関数は、システムコール番号53(
nsec
)をAX
レジスタに設定し、INT $64
命令でシステムコールを呼び出します。 - エラーチェックと
scratch
引数への値の格納ロジックが含まれています。
-
src/pkg/runtime/sys_plan9_amd64.s
:runtime·nsec
という新しいアセンブリ関数が追加されました。- この関数は、システムコール番号53を
BP
レジスタに設定し、SYSCALL
命令でシステムコールを呼び出します。
コアとなるコードの解説
src/pkg/runtime/os_plan9.c
の変更
--- a/src/pkg/runtime/os_plan9.c
+++ b/src/pkg/runtime/os_plan9.c
@@ -150,29 +150,13 @@ runtime·usleep(uint32 µs)
int64
runtime·nanotime(void)
{
- static int32 fd = -1;
- byte b[8];
- uint32 hi, lo;
-
- // As long as all goroutines share the same file
- // descriptor table we can get away with using
- // just a static fd. Without a lock the file can
- // be opened twice but that's okay.
- //
- // Using /dev/bintime gives us a latency on the
- // order of ten microseconds between two calls.
- //
- // The naïve implementation (without the cached
- // file descriptor) is roughly four times slower
- // in 9vx on a 2.16 GHz Intel Core 2 Duo.
-
- if(fd < 0 && (fd = runtime·open("/dev/bintime", OREAD|OCEXEC, 0)) < 0)
- return 0;
- if(runtime·pread(fd, b, sizeof b, 0) != sizeof b)
- return 0;
- hi = b[0]<<24 | b[1]<<16 | b[2]<<8 | b[3];
- lo = b[4]<<24 | b[5]<<16 | b[6]<<8 | b[7];
- return (int64)hi<<32 | (int64)lo;
+ int64 ns, scratch;
+
+ ns = runtime·nsec(&scratch);
+ // TODO(aram): remove hack after I fix _nsec in the pc64 kernel.
+ if(ns == 0)
+ return scratch;
+ return ns;
}
この変更は、runtime·nanotime
関数が時刻を取得するメカニズムを根本的に変えています。
- 削除されたコードは、
/dev/bintime
ファイルを開き、そこから8バイトのバイナリデータを読み取り、それを64ビットのナノ秒値に変換するロジックでした。これには、ファイルディスクリプタのキャッシュや、パフォーマンスに関するコメントが含まれていました。 - 新しいコードでは、
runtime·nsec(&scratch)
という新しい関数を呼び出しています。この関数は、nsec
システムコールを直接呼び出すためのGoランタイムのラッパーです。 if(ns == 0) return scratch;
という行は、nsec
システムコールが何らかの理由で0
を返した場合(おそらくエラーを示す)に、scratch
変数に格納された代替値(おそらくエラーコードや、別の方法で取得された時刻情報)を返すという一時的な回避策です。これは、TODO
コメントが示すように、当時のPlan 9 pc64カーネルにおけるnsec
システムコールの挙動に関する既知の問題に対応するためのものです。
src/pkg/runtime/os_plan9.h
の変更
--- a/src/pkg/runtime/os_plan9.h
+++ b/src/pkg/runtime/os_plan9.h
@@ -15,6 +15,7 @@ int32 runtime·plan9_tsemacquire(uint32 *addr, int32 ms);
int32 runtime·plan9_semrelease(uint32 *addr, int32 count);
int32 runtime·notify(void (*fn)(void*, int8*));
int32 runtime·noted(int32);
+int64 runtime·nsec(int64*);
void runtime·sigtramp(void*, int8*);
void runtime·sigpanic(void);
void runtime·goexitsall(int8*);
この変更は、runtime·nsec
関数のプロトタイプ宣言を追加しています。これにより、他のCファイルやGoのランタイムコードからこの関数を呼び出すことができるようになります。int64 runtime·nsec(int64*);
は、int64
型の戻り値を持ち、int64
型へのポインタを引数として受け取ることを示しています。このポインタは、前述のscratch
変数として使用されます。
src/pkg/runtime/sys_plan9_386.s
の変更
--- a/src/pkg/runtime/sys_plan9_386.s
+++ b/src/pkg/runtime/sys_plan9_386.s
@@ -64,6 +64,16 @@ TEXT runtime·plan9_tsemacquire(SB),NOSPLIT,$0
INT $64
RET
+TEXT runtime·nsec(SB),NOSPLIT,$0
+\tMOVL\t$53, AX
+\tINT\t$64
+\tCMPL\tAX, $-1
+\tJNE\t4(PC)
+\tMOVL\ta+0(FP), CX
+\tMOVL\tAX, 0(CX)
+\tMOVL\tAX, 4(CX)
+\tRET
+\
TEXT runtime·notify(SB),NOSPLIT,$0
MOVL $28, AX
INT $64
このアセンブリコードは、32ビットx86アーキテクチャ(386)向けのruntime·nsec
関数の実装です。
TEXT runtime·nsec(SB),NOSPLIT,$0
:runtime·nsec
という名前の関数を定義しています。NOSPLIT
はスタックフレームを分割しないことを示し、$0
はローカル変数に0バイトを割り当てることを示します。MOVL $53, AX
: システムコール番号53(Plan 9のnsec
システムコールに対応)をAX
レジスタに移動します。システムコール番号は通常AX
レジスタに渡されます。INT $64
: 割り込み番号64を発生させ、Plan 9カーネルにシステムコールを要求します。CMPL AX, $-1
: システムコールからの戻り値(AX
レジスタに格納されている)が-1
(エラーを示す一般的な値)と等しいかどうかを比較します。JNE 4(PC)
:AX
が-1
でない場合(エラーでない場合)、現在のプログラムカウンタから4バイト先にジャンプします。これは、エラー処理をスキップして関数を終了することを意味します。MOVL a+0(FP), CX
: 関数の最初の引数(scratch
ポインタ)をCX
レジスタにロードします。a+0(FP)
は、フレームポインタFP
からのオフセットで引数にアクセスするGoアセンブリの記法です。MOVL AX, 0(CX)
:AX
レジスタの値(システムコールの戻り値、エラーコード)をCX
が指すメモリ位置(scratch
変数)に格納します。MOVL AX, 4(CX)
: 32ビットシステムでは、64ビット値を格納するために2つの32ビットレジスタを使用することがあります。ここでは、AX
の値をscratch
変数の上位4バイトにも格納している可能性があります。これは、nsec
が64ビット値を返すため、そのエラーコードを64ビットのscratch
変数全体にコピーしていると考えられます。RET
: 関数から戻ります。
src/pkg/runtime/sys_plan9_amd64.s
の変更
--- a/src/pkg/runtime/sys_plan9_amd64.s
+++ b/src/pkg/runtime/sys_plan9_amd64.s
@@ -77,6 +77,11 @@ TEXT runtime·plan9_tsemacquire(SB),NOSPLIT,$0
SYSCALL
RET
+TEXT runtime·nsec(SB),NOSPLIT,$0
+\tMOVQ\t$53, BP
+\tSYSCALL
+\tRET
+\
TEXT runtime·notify(SB),NOSPLIT,$0
MOVQ $28, BP
SYSCALL
このアセンブリコードは、64ビットx86アーキテクチャ(amd64)向けのruntime·nsec
関数の実装です。
TEXT runtime·nsec(SB),NOSPLIT,$0
:runtime·nsec
という名前の関数を定義しています。MOVQ $53, BP
: システムコール番号53をBP
レジスタに移動します。64ビットシステムコールでは、システムコール番号を特定のレジスタ(この場合はBP
)に渡すのが一般的です。SYSCALL
:SYSCALL
命令を実行し、カーネルにシステムコールを要求します。RET
: 関数から戻ります。64ビットシステムコールでは、戻り値は通常AX
レジスタに格納されますが、このアセンブリコードでは明示的なMOVQ
命令は記述されていません。これは、Goのアセンブリリンカが自動的に戻り値を処理するか、またはnsec
システムコールが直接AX
レジスタに結果を書き込むためと考えられます。
関連リンク
- Go CL (Code Review): https://golang.org/cl/104570043
参考にした情報源リンク
- 9p.io - Plan 9 from Bell Labs: https://9p.io/
- Plan 9 from Bell Labs - Wikipedia: https://en.wikipedia.org/wiki/Plan_9_from_Bell_Labs
- Plan 9
nsec
system call documentation (example on GitHub): https://github.com/plan9/plan9/blob/master/sys/src/libc/9sys/nsec.c - Plan 9
bintime
device documentation (example on GitHub): https://github.com/plan9/plan9/blob/master/sys/src/9/bintime.c - Go Assembly Language (Go Wiki): https://go.dev/doc/asm
- x86 calling conventions (Wikipedia): https://en.wikipedia.org/wiki/X86_calling_conventions
- Plan 9 System Calls (example on GitHub): https://github.com/plan9/plan9/blob/master/sys/src/9/syscall.h (for syscall numbers)