[インデックス 15435] ファイルの概要
このコミットは、Go言語のランタイムおよびシステムコールパッケージにおける、Plan 9オペレーティングシステム上でのnanotime
(ナノ秒精度時刻)の取得方法に関する変更を扱っています。特に、64ビット版(amd64)のPlan 9におけるnanotime
の実装を、32ビット版(386)とは異なるシステムコールベースのアプローチに分離し、最適化を図っています。
コミット
commit d2326febd5164e5b8123f6507c34e800a57ce851
Author: Akshat Kumar <seed@mail.nanosouffle.net>
Date: Tue Feb 26 01:56:08 2013 +0100
syscall, runtime: Plan 9: use nanotime syscall on amd64
Separates the implementation of nanotime on 64-bit
version of Plan 9 from that on the 32-bit version.
The former uses a syscall.
R=rsc, rminnich, ality
CC=golang-dev
https://golang.org/cl/7379051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d2326febd5164e5b8123f6507c34e800a57ce851
元コミット内容
このコミットは、Go言語のPlan 9向け実装において、nanotime
関数の動作を変更します。具体的には、64ビットアーキテクチャ(amd64)のPlan 9では、nanotime
が直接システムコールを利用するように変更されます。これにより、32ビットアーキテクチャ(386)のPlan 9で引き続き使用される/dev/bintime
からのファイル読み取りによる実装とは異なる、より効率的な方法が採用されます。
変更の要点は以下の通りです。
runtime
パッケージ内のnanotime
実装が、amd64と386で分離されます。- amd64では、アセンブリコードを通じて直接
nanotime
システムコール(システムコール番号60)を呼び出すようになります。 - 386では、既存の
/dev/bintime
ファイルからの読み取りによる実装が維持されますが、そのC言語コードはruntime/thread_plan9.c
からruntime/time_plan9_386.c
という新しいファイルに移動されます。 syscall
パッケージのGettimeofday
関数は、アーキテクチャ固有のnanotime
関数を呼び出すように抽象化されます。
変更の背景
この変更の背景には、Go言語がサポートする様々なオペレーティングシステムやアーキテクチャにおいて、時間取得の効率性と正確性を向上させるという目的があります。特にPlan 9のようなユニークなOS環境では、一般的なUnix系システムとは異なるアプローチが必要となる場合があります。
以前のnanotime
の実装は、32ビットと64ビットの両方で/dev/bintime
という特殊なデバイスファイルからナノ秒単位の時刻情報を読み取るC言語のコードを使用していました。この方法は、ファイルディスクリプタのオープンや読み取りといったI/O操作を伴うため、オーバーヘッドが発生し、特に頻繁に呼び出される時間取得関数においてはパフォーマンスのボトルネックとなる可能性がありました。
64ビットアーキテクチャのPlan 9では、より直接的なシステムコールを通じてナノ秒精度時刻を取得する機能が提供されていると考えられます。このシステムコールを利用することで、ファイルI/Oを介するよりも高速かつ効率的に時刻情報を取得できるようになります。
このコミットは、このようなアーキテクチャ固有の最適化の機会を捉え、64ビット版Plan 9でのnanotime
のパフォーマンスを改善することを目的としています。同時に、32ビット版Plan 9では既存の安定したメカニズムを維持しつつ、コードの分離によって将来的なメンテナンス性も向上させています。
前提知識の解説
このコミットを理解するためには、以下の概念についての知識が役立ちます。
1. Go言語のランタイムとシステムコール
- Goランタイム (runtime): Goプログラムの実行を管理する低レベルのコンポーネントです。ガベージコレクション、スケジューリング、メモリ管理、そしてOSとのインタラクション(システムコールなど)を担当します。
nanotime
のようなOSに依存する機能は、通常ランタイム内で実装されます。 - システムコール (syscall): オペレーティングシステムが提供するサービスをプログラムが利用するためのインターフェースです。ファイルI/O、メモリ管理、プロセス制御、そして時刻取得など、OSカーネルの機能にアクセスするために使用されます。システムコールは通常、アセンブリ言語で実装された低レベルの関数を通じて呼び出されます。
syscall
パッケージ: Go言語の標準ライブラリの一部で、OSのシステムコールにアクセスするためのGo言語のインターフェースを提供します。これにより、Goプログラムから直接OSの機能を利用できます。
2. Plan 9オペレーティングシステム
- Plan 9 from Bell Labs: Unixの後継としてベル研究所で開発された分散オペレーティングシステムです。Unixとは異なる設計思想を持ち、特に「すべてがファイルである」という原則を徹底しています。デバイス、プロセス、ネットワークリソースなど、あらゆるものがファイルシステム上のファイルとして表現されます。
/dev/bintime
: Plan 9における特殊なデバイスファイルの一つです。このファイルを読み取ることで、システムが提供するナノ秒精度時刻を取得できます。これは、Unix系システムにおけるgettimeofday
やclock_gettime
のような関数に相当する機能を提供します。- amd64と386: それぞれ64ビットと32ビットのx86アーキテクチャを指します。同じOSでも、アーキテクチャによってシステムコールの呼び出し規約や利用可能なシステムコールが異なる場合があります。
3. アセンブリ言語とシステムコール呼び出し
- アセンブリ言語 (.sファイル): コンピュータのプロセッサが直接実行できる機械語に非常に近い低レベルのプログラミング言語です。Go言語のランタイムやシステムコールインターフェースの多くは、パフォーマンスやOSとの直接的なインタラクションのためにアセンブリ言語で記述されています。
SYSCALL
命令: x86-64アーキテクチャにおけるシステムコールを呼び出すための命令です。この命令を実行する前に、システムコール番号や引数を特定のレジスタ(例:AX
,BP
など)に設定する必要があります。- システムコール番号: 各システムコールには一意の番号が割り当てられています。プログラムは、呼び出したいシステムコールの番号をレジスタに設定して
SYSCALL
命令を実行することで、そのシステムコールを呼び出します。
4. nanotime
関数
nanotime
は、Go言語の内部でナノ秒単位の精度で現在の時刻を取得するために使用される関数です。これは、time.Now()
のような高レベルの時刻関数や、プロファイリング、ベンチマークなど、高精度な時間測定が必要な場面で利用されます。
これらの知識を前提として、コミットがどのようにGoランタイムとPlan 9の特性を考慮してnanotime
の実装を最適化しているかを深く理解することができます。
技術的詳細
このコミットの技術的詳細は、Go言語のランタイムとシステムコール層における、Plan 9環境でのnanotime
の実装戦略の変更に集約されます。
1. nanotime
実装のアーキテクチャ分離
- 変更前:
src/pkg/runtime/thread_plan9.c
に、32ビットと64ビットの両方のPlan 9アーキテクチャで共通して使用されるruntime·nanotime
のC言語実装が存在していました。この実装は、/dev/bintime
ファイルを開き、そこから8バイトのバイナリデータを読み取り、それをナノ秒値にデコードするというものでした。これはファイルI/Oを伴うため、一定のオーバーヘッドがありました。 - 変更後:
amd64
(64ビット):nanotime
の取得に直接システムコールを利用するようになりました。src/pkg/runtime/sys_plan9_amd64.s
に、runtime·nanotime
というアセンブリ関数が追加されました。この関数は、AX
レジスタに0x8000
(システムコール呼び出しの一般的なプレフィックス)を設定し、BP
レジスタにシステムコール番号60
を設定した後、SYSCALL
命令を実行します。これにより、Plan 9カーネルが提供するnanotime
システムコールが直接呼び出され、結果が返されます。src/pkg/syscall/syscall_plan9_amd64.go
に、Go言語からこのシステムコールを呼び出すためのラッパー関数nanotime()
が追加されました。この関数はRawSyscall(SYS_NANOTIME, 0, 0, 0)
を使用し、アセンブリで定義されたシステムコールを呼び出します。src/pkg/syscall/zsysnum_plan9_amd64.go
に、SYS_NANOTIME = 60
という定数が追加され、システムコール番号が明示されました。
386
(32ビット): 既存の/dev/bintime
からの読み取りアプローチが維持されますが、そのC言語実装はsrc/pkg/runtime/thread_plan9.c
からsrc/pkg/runtime/time_plan9_386.c
という新しいファイルに分離されました。これにより、32ビット版と64ビット版のnanotime
実装が明確に区別され、コードの保守性が向上しました。src/pkg/syscall/syscall_plan9_386.go
に、/dev/bintime
を読み取るGo言語のnanotime()
関数が追加されました。この関数は、ファイルディスクリプタをキャッシュしないというコメント(TODO)が残されており、将来的な最適化の余地があることを示唆しています。
2. Gettimeofday
の抽象化
src/pkg/syscall/syscall_plan9.go
内のGettimeofday
関数が変更されました。以前は、この関数内で直接/dev/bintime
を開き、読み取り、デコードするロジックが含まれていました。- 変更後は、
Gettimeofday
はアーキテクチャ固有のnanotime()
関数(syscall_plan9_386.go
またはsyscall_plan9_amd64.go
で定義される)を呼び出すように簡素化されました。これにより、Gettimeofday
はnanotime
の実装詳細から切り離され、よりクリーンなインターフェースとなりました。
3. パフォーマンスと設計の考慮事項
- パフォーマンス向上:
amd64
におけるシステムコールへの切り替えは、ファイルI/Oのオーバーヘッドを排除し、nanotime
の呼び出しをより高速にすることを目的としています。特に、高頻度で時刻情報を取得するアプリケーションやランタイム内部の処理において、この改善は重要です。 - コードの分離と保守性:
nanotime
の実装をアーキテクチャごとに分離し、C言語のコードを新しいファイルに移動することで、コードベースのモジュール性が向上し、将来的なメンテナンスや特定のアーキテクチャに特化した最適化が容易になります。 - Plan 9の特性: Plan 9の「すべてがファイルである」という設計思想は強力ですが、パフォーマンスがクリティカルな場面では、より低レベルなシステムコールへの直接アクセスが望ましい場合があります。このコミットは、そのバランスを取る一例と言えます。
このコミットは、Go言語が異なるOSやアーキテクチャの特性をどのように吸収し、それぞれの環境で最適なパフォーマンスを引き出すように設計されているかを示す良い例です。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
-
src/pkg/runtime/sys_plan9_amd64.s
:runtime·nanotime
という新しいアセンブリ関数が追加されました。- この関数は、
MOVQ $0x8000, AX
とMOVQ $60, BP
でシステムコール番号を設定し、SYSCALL
命令で直接Plan 9のnanotime
システムコールを呼び出します。
--- a/src/pkg/runtime/sys_plan9_amd64.s +++ b/src/pkg/runtime/sys_plan9_amd64.s @@ -104,6 +104,12 @@ TEXT runtime·plan9_semrelease(SB),7,$0 SYSCALL RET +TEXT runtime·nanotime(SB),7,$0 + MOVQ $0x8000, AX + MOVQ $60, BP + SYSCALL + RET + TEXT runtime·rfork(SB),7,$0 MOVQ $0x8000, AX MOVQ $19, BP // rfork
-
src/pkg/runtime/thread_plan9.c
:- 既存の
runtime·nanotime
のC言語実装が削除されました。この実装は/dev/bintime
を読み取るものでした。
--- a/src/pkg/runtime/thread_plan9.c +++ b/src/pkg/runtime/thread_plan9.c @@ -115,34 +115,6 @@ runtime·usleep(uint32 µs) runtime·sleep(ms); } -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((byte*)"/dev/bintime", OREAD|OCEXEC)) < 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; -} - void time·now(int64 sec, int32 nsec) {
- 既存の
-
src/pkg/runtime/time_plan9_386.c
:thread_plan9.c
から削除されたruntime·nanotime
のC言語実装が、この新しいファイルに移動されました。これは32ビット版Plan 9専用の実装となります。
--- /dev/null +++ b/src/pkg/runtime/time_plan9_386.c @@ -0,0 +1,34 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "runtime.h" +#include "os_GOOS.h" + +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((byte*)"/dev/bintime", OREAD|OCEXEC)) < 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; +}
-
src/pkg/syscall/syscall_plan9.go
:Gettimeofday
関数が簡素化され、内部でnanotime()
を呼び出すようになりました。
--- a/src/pkg/syscall/syscall_plan9.go +++ b/src/pkg/syscall/syscall_plan9.go @@ -312,29 +312,12 @@ func DecodeBintime(b []byte) (nsec int64, err error) { return } -func Gettimeofday(tv *Timeval) (err error) { - // TODO(paulzhol): - // avoid reopening a file descriptor for /dev/bintime on each call, - // use lower-level calls to avoid allocation. - - var b [8]byte - var nsec int64 - - fd, e := Open("/dev/bintime", O_RDONLY) +func Gettimeofday(tv *Timeval) error { + nsec, e := nanotime() if e != nil { return e } - defer Close(fd) - - if _, e = Pread(fd, b[:], 0); e != nil { - return e - } - - if nsec, e = DecodeBintime(b[:]); e != nil { - return e - } *tv = NsecToTimeval(nsec) - return e }
-
src/pkg/syscall/syscall_plan9_386.go
:- 32ビット版Plan 9向けの
nanotime()
Go関数が追加されました。これは/dev/bintime
を読み取るロジックを含みます。
--- a/src/pkg/syscall/syscall_plan9_386.go +++ b/src/pkg/syscall/syscall_plan9_386.go @@ -5,3 +5,28 @@ package syscall func Getpagesize() int { return 0x1000 } + +func nanotime() (nsec int64, err error) { + // TODO(paulzhol): + // avoid reopening a file descriptor for /dev/bintime on each call, + // use lower-level calls to avoid allocation. + + var b [8]byte + nsec = -1 + + fd, err := Open("/dev/bintime", O_RDONLY) + if err != nil { + return + } + defer Close(fd) + + if _, err = Pread(fd, b[:], 0); err != nil { + return + } + + if nsec, err = DecodeBintime(b[:]); err != nil { + return -1, err + } + + return +}
- 32ビット版Plan 9向けの
-
src/pkg/syscall/syscall_plan9_amd64.go
:- 64ビット版Plan 9向けの
nanotime()
Go関数が追加されました。これはSYS_NANOTIME
システムコールを呼び出します。
--- a/src/pkg/syscall/syscall_plan9_amd64.go +++ b/src/pkg/syscall/syscall_plan9_amd64.go @@ -5,3 +5,10 @@ package syscall func Getpagesize() int { return 0x200000 } + +// Used by Gettimeofday, which expects +// an error return value. +func nanotime() (int64, error) { + r1, _, _ := RawSyscall(SYS_NANOTIME, 0, 0, 0) + return int64(r1), nil +}
- 64ビット版Plan 9向けの
-
src/pkg/syscall/zsysnum_plan9_amd64.go
:SYS_NANOTIME
システムコール番号が追加されました。
--- a/src/pkg/syscall/zsysnum_plan9_amd64.go +++ b/src/pkg/syscall/zsysnum_plan9_amd64.go @@ -44,4 +44,5 @@ const ( SYS_AWAIT = 47 SYS_PREAD = 50 SYS_PWRITE = 51 +\tSYS_NANOTIME = 60 )
コアとなるコードの解説
このコミットの核心は、Go言語がPlan 9オペレーティングシステム上でナノ秒精度時刻を取得するメカニズムを、アーキテクチャ(32ビット vs 64ビット)に応じて最適化している点にあります。
1. amd64
におけるシステムコール利用 (src/pkg/runtime/sys_plan9_amd64.s
と src/pkg/syscall/syscall_plan9_amd64.go
)
- アセンブリコード (
sys_plan9_amd64.s
):TEXT runtime·nanotime(SB),7,$0
は、Goランタイムが呼び出すnanotime
関数のアセンブリ実装を定義しています。SB
は静的ベースポインタ、7
はフレームサイズ、$0
は引数サイズを示します。MOVQ $0x8000, AX
とMOVQ $60, BP
は、システムコールを呼び出すための準備です。Plan 9のシステムコール規約では、AX
レジスタにシステムコール呼び出しのプレフィックス(通常0x8000
)を設定し、BP
レジスタにシステムコール番号を設定します。ここで60
がnanotime
システムコールに割り当てられた番号です。SYSCALL
命令は、設定されたレジスタの値に基づいてカーネルにシステムコールを要求します。カーネルは時刻情報を計算し、結果をレジスタ(通常AX
)に返します。RET
は関数から戻ります。
- Goラッパー (
syscall_plan9_amd64.go
):func nanotime() (int64, error)
は、Go言語のコードからアセンブリで実装されたnanotime
システムコールを呼び出すためのインターフェースです。RawSyscall(SYS_NANOTIME, 0, 0, 0)
は、Goのsyscall
パッケージが提供する低レベルの関数で、直接システムコールを呼び出します。SYS_NANOTIME
は、zsysnum_plan9_amd64.go
で定義されたシステムコール番号60
です。引数0, 0, 0
は、nanotime
システムコールが追加の引数を必要としないことを示します。r1, _, _ := ...
は、システムコールからの戻り値を受け取ります。r1
にはシステムコールの結果(ナノ秒値)が格納され、_
は他の戻り値(エラーコードなど)がこのコンテキストでは無視されることを示します。return int64(r1), nil
は、取得したナノ秒値をint64
型にキャストして返し、エラーがないことを示します。
2. 386
における/dev/bintime
利用の維持と分離 (src/pkg/runtime/time_plan9_386.c
と src/pkg/syscall/syscall_plan9_386.go
)
- C言語実装の移動 (
time_plan9_386.c
):- 以前
thread_plan9.c
にあったruntime·nanotime
のC言語実装が、この新しいファイルに移動されました。このコードは、Plan 9の「すべてがファイルである」という原則に従い、/dev/bintime
という特殊なデバイスファイルを開き、そこから8バイトのバイナリデータを読み取ってナノ秒値に変換します。 static int32 fd = -1;
は、ファイルディスクリプタをキャッシュしようとする試みですが、コメントにあるように、ロックなしで静的変数を使用しているため、複数回オープンされる可能性があり、また「素朴な実装」であると述べられています。これは、この方法が理想的ではないが、32ビット環境では他に良い選択肢がないか、あるいはこの時点での優先度が低かったことを示唆しています。
- 以前
- Goラッパー (
syscall_plan9_386.go
):func nanotime() (nsec int64, err error)
は、32ビット版Plan 9向けのnanotime
Go関数です。- この関数は、
syscall.Open
、syscall.Pread
、DecodeBintime
といったGoのsyscall
パッケージの関数を組み合わせて、/dev/bintime
からの読み取りとデコードを実行します。 defer Close(fd)
は、関数が終了する際にファイルディスクリプタを確実に閉じるためのGoの機能です。
3. Gettimeofday
の抽象化 (src/pkg/syscall/syscall_plan9.go
)
func Gettimeofday(tv *Timeval) error
は、Timeval
構造体に現在の時刻を設定する関数です。- 変更前は、この関数自体が
/dev/bintime
の読み取りロジックを持っていましたが、変更後はnsec, e := nanotime()
という形で、アーキテクチャ固有のnanotime()
関数を呼び出すようになりました。 - これにより、
Gettimeofday
はnanotime
の具体的な実装(システムコールかファイル読み取りか)に依存しなくなり、コードのモジュール性と可読性が向上しました。
このコミットは、Go言語が異なるOSの特性(Plan 9のファイルシステムベースのデバイスアクセスと、より伝統的なシステムコールインターフェース)をどのように抽象化し、各アーキテクチャで最適なパフォーマンスと保守性を実現しようとしているかを示す典型的な例です。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Plan 9 from Bell Labs: https://9p.io/plan9/
- Go言語のシステムコールパッケージ (
syscall
): https://pkg.go.dev/syscall - Go言語のランタイムパッケージ (
runtime
): https://pkg.go.dev/runtime
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- Go言語のコードレビューシステム (Gerrit): https://go.dev/cl/ (コミットメッセージに記載されている
https://golang.org/cl/7379051
は、このGerritシステムへのリンクです。) - Plan 9のシステムコールに関する情報 (一般的なOSのシステムコール概念): https://en.wikipedia.org/wiki/System_call
- x86-64アセンブリ言語のシステムコール規約に関する情報 (一般的なアセンブリの知識): https://en.wikipedia.org/wiki/X86_calling_conventions#System_calls