[インデックス 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向けのnanotimeGo関数です。- この関数は、
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