Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 13288] ファイルの概要

このコミットは、GoランタイムがmacOS (OS X) 上で時刻を取得する際のパフォーマンスを大幅に改善するための変更です。具体的には、gettimeofdayシステムコールを直接呼び出す代わりに、OS Xの「comm page」と呼ばれる共有メモリ領域から直接時刻情報を読み取る「vsyscall」のような最適化された手法を386アーキテクチャ向けに導入しています。これにより、システムコールによるカーネルモードへの切り替えが不要になり、時刻取得のオーバーヘッドが劇的に削減されます。

コミット

commit c7be4defe31fbcbae028b382c707361d28d5d7ca
Author: Russ Cox <rsc@golang.org>
Date:   Tue Jun 5 16:23:30 2012 -0400

    runtime: use OS X vsyscall for gettimeofday (386)
    
    amd64 was done in CL 6275056.
    
    We don't attempt to handle machines with clock speeds
    less than 1 GHz. Those will fall back to the system call.
    
    benchmark       old ns/op    new ns/op    delta
    BenchmarkNow          364           38  -89.53%
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/6307045

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/c7be4defe31fbcbae028b382c707361d28d5d7ca

元コミット内容

このコミットは、GoランタイムがmacOS (OS X) の386アーキテクチャ上でgettimeofdayシステムコールを使用して時刻を取得する際のパフォーマンスを改善することを目的としています。以前のバージョンでは、time·now関数とruntime·nanotime関数が直接INT $0x80命令(システムコール)を発行して時刻を取得していました。これは一般的なシステムコール呼び出しのメカニズムであり、カーネルモードへのコンテキストスイッチを伴うため、オーバーヘッドが発生します。

コミットメッセージによると、同様の最適化は既にAMD64アーキテクチャ向けにCL 6275056で実装済みであり、このコミットは386アーキテクチャへの対応を補完するものです。

ベンチマーク結果として、BenchmarkNowの実行時間が364 ns/opから38 ns/opへと大幅に短縮され、約89.53%の改善が示されています。これは、時刻取得処理が非常に頻繁に呼び出される可能性があるため、アプリケーション全体のパフォーマンスに大きな影響を与える可能性があります。

また、この最適化はクロック速度が1GHz未満の古いマシンでは適用されず、その場合は従来のシステムコールにフォールバックするという注意書きがあります。これは、高速な時刻取得に必要なハードウェア機能や、計算の精度が低いCPUでは最適化の恩恵が受けられない、あるいは不正確になる可能性があるためと考えられます。

変更の背景

プログラムが現在の時刻を取得する操作は、非常に頻繁に行われる基本的な処理の一つです。例えば、ログのタイムスタンプ、パフォーマンス計測、タイマー、スケジューリングなど、多岐にわたる場面で利用されます。従来のシステムコール(gettimeofdayなど)を介した時刻取得は、ユーザーモードからカーネルモードへのコンテキストスイッチを伴うため、一定のオーバーヘッドが発生します。このオーバーヘッドは、特に高頻度で時刻取得を行うアプリケーションにおいて、無視できないパフォーマンスボトルネックとなる可能性があります。

macOS (OS X) のようなモダンなオペレーティングシステムでは、このような高頻度で呼び出される処理のパフォーマンスを改善するために、「vsyscall (virtual system call)」や「vdso (virtual dynamic shared object)」といったメカニズムを提供しています。これらは、カーネルがユーザー空間に特定のシステムコール実装の一部をマッピングすることで、コンテキストスイッチなしに直接実行できるようにするものです。これにより、システムコール呼び出しのコストを大幅に削減し、パフォーマンスを向上させることができます。

このコミットの背景には、GoランタイムがmacOSの386アーキテクチャ上で、より効率的な時刻取得メカニズムを利用することで、time.Now()runtime.nanotime()といった時刻関連関数のパフォーマンスを向上させるという明確な目的があります。既にAMD64アーキテクチャでは同様の最適化が適用されており、このコミットは386アーキテクチャへの対応を完了させるものです。

前提知識の解説

このコミットを理解するためには、以下の技術的な概念について知っておく必要があります。

  1. システムコール (System Call):

    • アプリケーション(ユーザーモード)がオペレーティングシステム(カーネルモード)の機能(ファイルI/O、メモリ管理、プロセス管理、時刻取得など)を利用するためのインターフェースです。
    • システムコールを呼び出す際には、ユーザーモードからカーネルモードへのコンテキストスイッチが発生します。これはCPUのモード変更、レジスタの保存・復元、特権レベルの変更などを伴うため、一定のオーバーヘッドがあります。
  2. vsyscall (Virtual System Call) / vdso (Virtual Dynamic Shared Object):

    • 一部のシステムコールは非常に頻繁に呼び出され、そのたびにコンテキストスイッチが発生するとパフォーマンスに悪影響を与えます。
    • vsyscallやvdsoは、このような頻繁に呼び出されるシステムコール(特に時刻取得など)の高速化のために導入されたメカニズムです。
    • カーネルが、これらのシステムコールの一部または全部の実装をユーザー空間にマッピングされた共有メモリ領域(「comm page」など)に配置します。
    • これにより、アプリケーションはシステムコールを直接呼び出す代わりに、この共有メモリ領域内のコードを直接実行できるようになり、カーネルモードへのコンテキストスイッチなしに処理を完了できます。結果として、オーバーヘッドが大幅に削減され、パフォーマンスが向上します。
  3. OS X (macOS) の Comm Page:

    • macOS (OS X) におけるvsyscallの実装の一つで、カーネルがユーザー空間にマッピングする共有メモリページです。
    • このページには、システム時刻に関する情報や、CPUの能力に関する情報などが格納されており、ユーザー空間のアプリケーションが直接読み取ることができます。
    • コミット内の0xffff0000というアドレスは、このcomm pageのベースアドレスを示しています。
    • cpu_capabilities.hというヘッダーファイルが参照されており、このファイルにはcomm page内の各種オフセット(nt_tsc_base, nt_scale, gtod_generationなど)が定義されています。これらは、時刻計算に必要な基準値やスケーリング係数、世代カウンタなどを指します。
  4. RDTSC (Read Time-Stamp Counter) 命令:

    • Intel x86アーキテクチャのCPUが提供する命令で、CPUが起動してからのクロックサイクル数をカウントする64ビットのタイムスタンプカウンタ(TSC)の値を読み取ります。
    • 非常に高精度で高速な時刻情報を提供しますが、TSCの周波数はCPUのクロック速度に依存し、省電力機能やマルチコア環境での同期問題など、使用には注意が必要です。
    • このコミットでは、TSCの値をcomm pageから取得したスケーリング係数などと組み合わせて、実際のナノ秒単位の時刻に変換しています。
  5. アセンブリ言語 (Assembly Language):

    • CPUが直接実行できる機械語命令を人間が読める形式で記述した低レベルなプログラミング言語です。
    • Goランタイムのパフォーマンスが重要な部分や、特定のハードウェア命令(RDTSCなど)を直接利用する必要がある部分では、アセンブリ言語が使用されます。
    • このコミットの変更は、sys_darwin_386.sというアセンブリファイルで行われています。
  6. Goのtime.Now()runtime.nanotime():

    • time.Now()は、現在のローカル時刻をtime.Time型で返します。
    • runtime.nanotime()は、Goランタイム内部で使用される高分解能なモノトニック(単調増加)な時刻をナノ秒単位で返します。これは、経過時間の計測などに使用され、システム時刻の変更(NTP同期など)の影響を受けません。
    • これらの関数は、最終的にOSから時刻情報を取得する低レベルなメカニズムに依存しています。

技術的詳細

このコミットの核心は、macOS (OS X) の386アーキテクチャにおいて、gettimeofdayシステムコールを介した時刻取得を、より高速な「comm page」からの直接読み取りに置き換えることです。

旧実装 (time·now / runtime·nanotime):

TEXT time·now(SB), 7, $32
	LEAL	12(SP), AX	// must be non-nil, unused
	MOVL	AX, 4(SP)
	MOVL	$0, 8(SP)	// time zone pointer
	MOVL	$116, AX    // gettimeofday syscall number
	INT	$0x80       // system call
	MOVL	DX, BX
	// ... sec and usec conversion ...
	RET

旧実装では、gettimeofdayシステムコール(システムコール番号116)をINT $0x80命令で呼び出していました。これは、ユーザーモードからカーネルモードへのコンテキストスイッチを伴い、パフォーマンス上のボトルネックとなっていました。

新実装 (runtime·now): 新しいruntime·now関数は、以下のステップで時刻を取得します。

  1. Comm Pageベースアドレスの設定: MOVL $0xffff0000, BP /* comm page base */ 0xffff0000は、OS Xのcomm pageがマッピングされている仮想アドレスのベースです。このアドレスをベースポインタBPに設定し、そこからのオフセットで各種時刻関連情報にアクセスします。

  2. CPU能力のチェック: MOVL cpu_capabilities(BP), AX TESTL $0x4000, AX JNZ systime cpu_capabilitiesはcomm page内のオフセットで、CPUの機能フラグが格納されています。$0x4000は特定のフラグ(おそらく高速な時刻取得に必要な機能)を示しており、このフラグが立っていない(つまり、CPUが遅い、または必要な機能がない)場合は、systimeラベルにジャンプし、従来のシステムコールにフォールバックします。コミットメッセージにある「クロック速度が1GHz未満のマシン」への対応です。

  3. 一貫性のある時刻情報のスナップショット取得 (Timeloop): timeloop: MOVL gtod_generation(BP), BX TESTL BX, BX JZ systime MOVL nt_generation(BP), CX TESTL CX, CX JZ timeloop ... CMPL nt_generation(BP), CX JNE timeloop ... CMPL gtod_generation(BP), BX JNE timeloop comm page内の時刻情報は、カーネルによって非同期に更新される可能性があります。そのため、読み取った情報が一貫していることを保証するために、「世代カウンタ」(gtod_generationnt_generation)を使用します。

    • gtod_generation: gettimeofday関連の情報の世代カウンタ。
    • nt_generation: nanotime関連の情報の世代カウンタ。 ループの開始時と終了時にこれらのカウンタを読み取り、値が変わっていないことを確認します。もし値が変わっていれば、読み取り中に情報が更新されたことを意味するため、ループを繰り返して一貫性のあるスナップショットが取れるまで再試行します。TESTL BX, BXTESTL CX, CXは、カウンタがゼロでないことを確認しており、ゼロの場合は無効な状態と判断してシステムコールにフォールバックします。
  4. RDTSC命令によるタイムスタンプカウンタの読み取り: RDTSC RDTSC命令は、CPUのタイムスタンプカウンタ(TSC)の値をEDX:EAXレジスタペアに読み込みます。EDXに上位32ビット、EAXに下位32ビットが格納されます。

  5. Comm Pageからの時刻関連パラメータの読み取り: nt_tsc_base(BP): TSCの基準値。 nt_scale(BP): TSCからナノ秒への変換スケール係数。 nt_ns_base(BP): ナノ秒の基準値。 gtod_ns_base(BP): gettimeofdayのナノ秒基準値。 gtod_sec_base(BP): gettimeofdayの秒基準値。 これらの値は、スタックに保存され、後続の計算で使用されます。

  6. 時刻の計算: この部分が最も複雑で、TSCの値とcomm pageから取得したパラメータを用いて、現在の時刻をナノ秒単位で計算します。 計算式は以下のようになります(コミットコメントより): ((tsc - nt_tsc_base) * nt_scale) >> 32 + nt_ns_base - gtod_ns_base + gtod_sec_base*1e9

    • SUBL 0(SP), AX / SBBL 4(SP), DX: DX:AX = (tsc - nt_tsc_base) を計算します。これは、現在のTSC値から基準TSC値を引くことで、経過したTSCサイクル数を求めます。
    • 96ビット乗算と上位64ビットの抽出: MOVL DX, CX MOVL $0, DX MULL 8(SP) (ここでAXtsc - nt_tsc_baseの下位32ビット、8(SP)nt_scale。結果はDX:AXに格納される) MOVL DX, SI (上位32ビットをSIに保存) MOVL CX, AX (AXtsc - nt_tsc_baseの上位32ビット、CXは元のDXの値) MOVL $0, DX MULL 8(SP) (ここでAXtsc - nt_tsc_baseの上位32ビット、8(SP)nt_scale。結果はDX:AXに格納される) ADDL SI, AX / ADCL $0, DX (前の乗算結果の上位32ビットを加算し、キャリーを処理) この一連の命令は、((tsc - nt_tsc_base) * nt_scale)の96ビット積の上位64ビットを効率的に計算しています。これは、((x_low * y) >> 32) + (x_high * y)という形式で、64ビットの結果を得るための一般的なアセンブリテクニックです。
    • オフセットと基準値の加減算: ADDL 12(SP), AX / ADCL 16(SP), DX: DX:AX += nt_ns_base SUBL 20(SP), AX / SBBL 24(SP), DX: DX:AX -= gtod_ns_base MOVL 28(SP), AX / MOVL 32(SP), DX: DX:AX = gtod_sec_base MOVL $1000000000, CX MULL CX: gtod_sec_baseをナノ秒に変換(*1e9ADDL SI, AX / ADCL DI, DX: 最終的なナノ秒値を加算 これらの計算により、現在の時刻がナノ秒単位でDX:AXレジスタペアに格納されます。
  7. time·nowruntime·nanotimeからの呼び出し: 旧実装では直接システムコールを呼び出していたtime·nowruntime·nanotimeは、新しいruntime·now関数を呼び出すように変更されました。 CALL runtime·now(SB) これにより、両方の関数が高速なvsyscallベースの時刻取得を利用するようになります。

フォールバック (systime): CPU能力のチェックや世代カウンタの一貫性チェックで問題が発生した場合、または何らかの理由でvsyscallが利用できない場合、コードはsystimeラベルにジャンプします。

systime:
	// Fall back to system call (usually first call in this thread)
	LEAL	12(SP), AX	// must be non-nil, unused
	MOVL	AX, 4(SP)
	MOVL	$0, 8(SP)	// time zone pointer
	MOVL	$116, AX
	INT	$0x80
	// ... sec and usec conversion to nsec ...
	RET

このsystimeブロックは、旧実装とほぼ同じで、gettimeofdayシステムコールを直接呼び出して時刻を取得します。これにより、最適化が適用できない環境でもGoプログラムが正しく動作することが保証されます。

コアとなるコードの変更箇所

変更はsrc/pkg/runtime/sys_darwin_386.sファイルに集中しています。

--- a/src/pkg/runtime/sys_darwin_386.s
+++ b/src/pkg/runtime/sys_darwin_386.s
@@ -63,40 +63,133 @@ TEXT runtime·setitimer(SB),7,$0
 	INT	$0x80
 	RET
 
-// func now() (sec int64, nsec int32)
-TEXT time·now(SB), 7, $32
-\tLEAL\t12(SP), AX\t// must be non-nil, unused
-\tMOVL\tAX, 4(SP)
-\tMOVL\t$0, 8(SP)\t// time zone pointer
-\tMOVL\t$116, AX
-\tINT\t$0x80
-\tMOVL\tDX, BX
+// OS X comm page time offsets
+// http://www.opensource.apple.com/source/xnu/xnu-1699.26.8/osfmk/i386/cpu_capabilities.h
+#define	cpu_capabilities	0x20
+#define	nt_tsc_base	0x50
+#define	nt_scale	0x58
+#define	nt_shift	0x5c
+#define	nt_ns_base	0x60
+#define	nt_generation	0x68
+#define	gtod_generation	0x6c
+#define	gtod_ns_base	0x70
+#define	gtod_sec_base	0x78
+
+// called from assembly
+// 64-bit unix nanoseconds returned in DX:AX.
+// I'd much rather write this in C but we need
+// assembly for the 96-bit multiply and RDTSC.
+TEXT runtime·now(SB),7,$40
+\tMOVL\t$0xffff0000, BP /* comm page base */
+\t
+\t// Test for slow CPU. If so, the math is completely
+\t// different, and unimplemented here, so use the
+\t// system call.
+\tMOVL\tcpu_capabilities(BP), AX
+\tTESTL\t$0x4000, AX
+\tJNZ\tsystime
+\n+\t// Loop trying to take a consistent snapshot
+\t// of the time parameters.\n+timeloop:\n+\tMOVL\tgtod_generation(BP), BX\n+\tTESTL\tBX, BX\n+\tJZ\tsystime\n+\tMOVL\tnt_generation(BP), CX\n+\tTESTL\tCX, CX\n+\tJZ\ttimeloop\n+\tRDTSC\n+\tMOVL\tnt_tsc_base(BP), SI\n+\tMOVL\t(nt_tsc_base+4)(BP), DI\n+\tMOVL\tSI, 0(SP)\n+\tMOVL\tDI, 4(SP)\n+\tMOVL\tnt_scale(BP), SI\n+\tMOVL\tSI, 8(SP)\n+\tMOVL\tnt_ns_base(BP), SI\n+\tMOVL\t(nt_ns_base+4)(BP), DI\n+\tMOVL\tSI, 12(SP)\n+\tMOVL\tDI, 16(SP)\n+\tCMPL\tnt_generation(BP), CX\n+\tJNE\ttimeloop\n+\tMOVL\tgtod_ns_base(BP), SI\n+\tMOVL\t(gtod_ns_base+4)(BP), DI\n+\tMOVL\tSI, 20(SP)\n+\tMOVL\tDI, 24(SP)\n+\tMOVL\tgtod_sec_base(BP), SI\n+\tMOVL\t(gtod_sec_base+4)(BP), DI\n+\tMOVL\tSI, 28(SP)\n+\tMOVL\tDI, 32(SP)\n+\tCMPL\tgtod_generation(BP), BX\n+\tJNE\ttimeloop\n+\n+\t// Gathered all the data we need. Compute time.\n+\t//\t((tsc - nt_tsc_base) * nt_scale) >> 32 + nt_ns_base - gtod_ns_base + gtod_sec_base*1e9\n+\t// The multiply and shift extracts the top 64 bits of the 96-bit product.\n+\tSUBL\t0(SP), AX // DX:AX = (tsc - nt_tsc_base)\n+\tSBBL\t4(SP), DX\n+\n+\t// We have x = tsc - nt_tsc_base - DX:AX to be\n+\t// multiplied by y = nt_scale = 8(SP), keeping the top 64 bits of the 96-bit product.\n+\t// x*y = (x&0xffffffff)*y + (x&0xffffffff00000000)*y\n+\t// (x*y)>>32 = ((x&0xffffffff)*y)>>32 + (x>>32)*y\n+\tMOVL\tDX, CX // SI = (x&0xffffffff)*y >> 32\n+\tMOVL\t$0, DX\n+\tMULL\t8(SP)\n+\tMOVL\tDX, SI\n \n-\t// sec is in AX, usec in BX\n-\tMOVL\tAX, sec+0(FP)\n-\tMOVL\t$0, sec+4(FP)\n-\tIMULL\t$1000, BX\n-\tMOVL\tBX, nsec+8(FP)\n+\tMOVL\tCX, AX // DX:AX = (x>>32)*y\n+\tMOVL\t$0, DX\n+\tMULL\t8(SP)\n+\n+\tADDL\tSI, AX\t// DX:AX += (x&0xffffffff)*y >> 32\n+\tADCL\t$0, DX\n+\t\n+\t// DX:AX is now ((tsc - nt_tsc_base) * nt_scale) >> 32.\n+\tADDL\t12(SP), AX\t// DX:AX += nt_ns_base\n+\tADCL\t16(SP), DX\n+\tSUBL\t20(SP), AX\t// DX:AX -= gtod_ns_base\n+\tSBBL\t24(SP), DX\n+\tMOVL\tAX, SI\t// DI:SI = DX:AX\n+\tMOVL\tDX, DI\n+\tMOVL\t28(SP), AX\t// DX:AX = gtod_sec_base*1e9\n+\tMOVL\t32(SP), DX\n+\tMOVL\t$1000000000, CX\n+\tMULL\tCX\n+\tADDL\tSI, AX\t// DX:AX += DI:SI\n+\tADCL\tDI, DX\n \tRET\n \n-// int64 nanotime(void) so really\n-// void nanotime(int64 *nsec)\n-TEXT runtime·nanotime(SB), 7, $32
+\nsystime:\n+\t// Fall back to system call (usually first call in this thread)\n \tLEAL\t12(SP), AX\t// must be non-nil, unused\n \tMOVL\tAX, 4(SP)\n \tMOVL\t$0, 8(SP)\t// time zone pointer\n \tMOVL\t$116, AX\n \tINT\t$0x80\n-\tMOVL\tDX, BX\n-\n-\t// sec is in AX, usec in BX\n+\t// sec is in AX, usec in DX\n \t// convert to DX:AX nsec\n+\tMOVL\tDX, BX\n \tMOVL\t$1000000000, CX\n \tMULL\tCX\n \tIMULL\t$1000, BX\n \tADDL\tBX, AX\n \tADCL\t$0, DX\n+\tRET\n+\n+// func now() (sec int64, nsec int32)\n+TEXT time·now(SB),7,$0\n+\tCALL\truntime·now(SB)\n+\tMOVL\t$1000000000, CX\n+\tDIVL\tCX\n+\tMOVL\tAX, sec+0(FP)\n+\tMOVL\t$0, sec+4(FP)\n+\tMOVL\tDX, nsec+8(FP)\n+\tRET\n \n+// int64 nanotime(void) so really\n+// void nanotime(int64 *nsec)\n+TEXT runtime·nanotime(SB),7,$0\n+\tCALL\truntime·now(SB)\n \tMOVL\tret+0(FP), DI\n \tMOVL\tAX, 0(DI)\n \tMOVL\tDX, 4(DI)\n```

## コアとなるコードの解説

変更の主要な部分は、`time·now`と`runtime·nanotime`関数が直接システムコールを呼び出す代わりに、新しく定義された`runtime·now`関数を呼び出すように変更された点です。

1.  **`#define`によるComm Pageオフセットの定義**:
    `cpu_capabilities`, `nt_tsc_base`, `nt_scale`, `nt_shift`, `nt_ns_base`, `nt_generation`, `gtod_generation`, `gtod_ns_base`, `gtod_sec_base`といった定数が定義されています。これらは、OS Xのcomm page内の特定のデータへのオフセットを示しており、時刻計算に必要な様々な基準値やスケーリング係数、世代カウンタにアクセスするために使用されます。これらの定義は、`http://www.opensource.apple.com/source/xnu/xnu-1699.26.8/osfmk/i386/cpu_capabilities.h`というAppleのオープンソースコードのヘッダーファイルに基づいています。

2.  **`TEXT runtime·now(SB),7,$40`**:
    この新しい関数が、高速な時刻取得ロジックの本体です。
    *   `MOVL $0xffff0000, BP /* comm page base */`: `BP`レジスタにcomm pageのベースアドレスを設定します。これにより、以降の命令で`offset(BP)`のように記述することで、comm page内のデータに直接アクセスできます。
    *   **CPU能力チェック**: `cpu_capabilities(BP)`からCPUの能力フラグを読み取り、特定のビット(`$0x4000`)がセットされているかを確認します。このビットは、高速な時刻取得に必要なハードウェア機能の有無を示唆していると考えられます。もし機能が不足している場合(`JNZ systime`)、処理は後述の`systime`ラベルにジャンプし、従来のシステムコールにフォールバックします。
    *   **`timeloop`**: このループは、comm pageから読み取る時刻関連データの一貫性を保証するために重要です。`gtod_generation`と`nt_generation`という2つの世代カウンタを読み取り、データの読み取り中にこれらのカウンタが変化していないことを確認します。変化していれば、データが更新されたことを意味するため、ループを繰り返して再試行します。これにより、不整合なデータに基づく時刻計算を防ぎます。
    *   **`RDTSC`**: CPUのタイムスタンプカウンタを読み取り、`EDX:EAX`レジスタに格納します。これは、非常に高精度なCPUサイクル数を提供します。
    *   **時刻計算**: 読み取ったTSC値と、comm pageから取得した`nt_tsc_base`(TSCの基準値)、`nt_scale`(TSCからナノ秒への変換スケール)、`nt_ns_base`(ナノ秒の基準値)、`gtod_ns_base`(`gettimeofday`のナノ秒基準値)、`gtod_sec_base`(`gettimeofday`の秒基準値)を用いて、複雑なアセンブリ命令シーケンスで現在の時刻をナノ秒単位で計算します。この計算には、96ビットの乗算(`MULL`命令を複数回使用)とシフト操作が含まれ、最終的に64ビットのナノ秒値が`DX:AX`レジスタペアに格納されます。これは、C言語では表現しにくい低レベルな最適化です。
    *   `RET`: 計算されたナノ秒値を`DX:AX`に保持して関数から戻ります。

3.  **`systime:`**:
    これは、高速なvsyscallベースの時刻取得が利用できない場合のフォールバックパスです。
    *   `INT $0x80`: 従来の`gettimeofday`システムコール(番号116)を呼び出します。
    *   システムコールから返された秒とマイクロ秒の値を、ナノ秒に変換する処理が行われます。

4.  **`TEXT time·now(SB),7,$0` と `TEXT runtime·nanotime(SB),7,$0` の変更**:
    これらの関数は、もはや直接システムコールを呼び出しません。代わりに、新しく定義された`runtime·now`関数を`CALL`命令で呼び出すように変更されました。
    *   `CALL runtime·now(SB)`: `runtime·now`関数を実行し、その結果(ナノ秒単位の時刻)を`DX:AX`レジスタで受け取ります。
    *   `time·now`では、受け取ったナノ秒値を秒とナノ秒に分割し、それぞれの戻り値レジスタに格納します。
    *   `runtime·nanotime`では、受け取ったナノ秒値をそのままポインタ経由で呼び出し元に渡します。

この変更により、GoランタイムはmacOSの386アーキテクチャ上で、可能な限り高速な時刻取得メカニズムを利用し、パフォーマンスを大幅に向上させることができました。

## 関連リンク

*   Goのコミットレビューシステム (Gerrit): [https://golang.org/cl/6307045](https://golang.org/cl/6307045)
*   Goの関連コミット (AMD64版): [https://golang.org/cl/6275056](https://golang.org/cl/6275056) (このコミットメッセージで参照されているCL)
*   Apple Open Source - xnu (macOSカーネル): [http://www.opensource.apple.com/source/xnu/](http://www.opensource.apple.com/source/xnu/)
    *   特に`osfmk/i386/cpu_capabilities.h`は、comm pageのオフセット定義の元となるファイルです。

## 参考にした情報源リンク

*   **vDSO - Wikipedia**: [https://en.wikipedia.org/wiki/VDSO](https://en.wikipedia.org/wiki/VDSO)
    *   vsyscall/vdsoの概念について理解を深めるために参照しました。
*   **RDTSC - Wikipedia**: [https://en.wikipedia.org/wiki/RDTSC](https://en.wikipedia.org/wiki/RDTSC)
    *   RDTSC命令の機能と使用上の注意点について参照しました。
*   **The Linux Kernel Module Programming Guide - Chapter 2. The Hello World Module**: [https://www.tldp.org/LDP/lkmpg/2.6/html/x86_64.html](https://www.tldp.org/LDP/lkmpg/2.6/html/x86_64.html)
    *   一般的なシステムコール呼び出しのメカニズム(`INT $0x80`など)について、より広範な文脈で理解するために参照しました。
*   **Goのソースコード (runtime/sys_darwin_386.s)**:
    *   コミットの変更内容を直接解析するために、GoのGitHubリポジトリの該当ファイルを参照しました。
*   **Apple Open Source - xnu (cpu_capabilities.h)**:
    *   コミットで参照されているcomm pageのオフセット定義の正確な情報源として参照しました。