[インデックス 14353] runtime: use vDSO for gettimeofday on linux/amd64
コミット
コミットハッシュ: 024a92c1da0b5e147df01ed184fe8d655af62900
Author: Anthony Martin ality@pbrane.org
Date: Wed Nov 7 18:29:31 2012 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/024a92c1da0b5e147df01ed184fe8d655af62900
元コミット内容
runtime: use vDSO for gettimeofday on linux/amd64
Intel Core 2 Duo (2.16 GHz) running 3.6.5-1-ARCH
benchmark old ns/op new ns/op delta
BenchmarkNow 1856 1034 -44.29%
R=rsc
CC=golang-dev
https://golang.org/cl/6826072
変更の背景
このコミットは、Goランタイムにおけるgettimeofday
システムコールのパフォーマンスを向上させることを目的としています。gettimeofday
は、現在の時刻(秒とマイクロ秒)を取得するために頻繁に呼び出される関数であり、その性能はアプリケーション全体の応答性に大きな影響を与えます。
従来のシステムコールは、ユーザー空間からカーネル空間へのコンテキストスイッチを伴うため、オーバーヘッドが発生します。特に、時刻取得のような頻繁に実行される操作では、このオーバーヘッドが累積してパフォーマンスのボトルネックとなる可能性があります。
コミットメッセージに示されているベンチマーク結果(BenchmarkNow
が1856 ns/opから1034 ns/opへ、44.29%の改善)は、この変更がGoプログラムの時刻取得処理において顕著な速度向上をもたらすことを明確に示しています。この改善は、特に高頻度で時刻情報を必要とするアプリケーションや、多数のゴルーチンが同時に動作するような環境で、より高いスループットと低いレイテンシを実現するのに貢献します。
前提知識の解説
システムコールとコンテキストスイッチ
オペレーティングシステム(OS)は、ハードウェアリソースへのアクセスや、ファイル操作、ネットワーク通信、時刻取得といった特権的な操作を管理します。ユーザープログラムがこれらの操作を行うためには、OSが提供する「システムコール」を呼び出す必要があります。
システムコールが呼び出されると、CPUはユーザーモードからカーネルモードへと実行コンテキストを切り替えます。この切り替えは「コンテキストスイッチ」と呼ばれ、CPUのレジスタの状態保存や復元、メモリ管理ユニット(MMU)の切り替えなど、一定のオーバーヘッドを伴います。頻繁なシステムコールは、このコンテキストスイッチのオーバーヘッドが積み重なり、パフォーマンスの低下を招く可能性があります。
gettimeofday
gettimeofday
は、Unix系OSで広く使われているシステムコールの一つで、現在の時刻を秒とマイクロ秒の精度で取得します。多くのプログラミング言語やライブラリが内部的にこのシステムコールを利用して、時刻関連の機能を提供しています。
vDSO (virtual Dynamic Shared Object)
vDSOは、Linuxカーネルが提供する最適化メカニズムの一つです。これは、特定の頻繁に呼び出されるシステムコール(例: gettimeofday
, clock_gettime
, getcpu
など)を、カーネル空間へのコンテキストスイッチなしにユーザー空間で直接実行できるようにするものです。
vDSOは、カーネルが起動時に生成し、各プロセスの仮想アドレス空間にマッピングする共有ライブラリのようなものです。このライブラリには、時刻情報など、カーネルがユーザー空間に公開しても安全なデータや、簡単な計算で結果が得られる関数の実装が含まれています。これにより、ユーザープログラムはシステムコールを呼び出す代わりに、vDSOが提供する関数を通常の関数呼び出しのように実行でき、コンテキストスイッチのオーバーヘッドを回避してパフォーマンスを向上させることができます。
vDSOが利用できない場合や、何らかの理由でvDSO経由での実行が失敗した場合は、自動的に従来のシステムコールにフォールバックする仕組みが備わっています。
技術的詳細
このコミットの核心は、GoランタイムがLinux/amd64環境でgettimeofday
を呼び出す際に、従来のシステムコールではなくvDSOを利用するように変更した点にあります。
Goのtime.now
関数は、内部的にOSの時刻取得メカニズムを利用しています。Linux/amd64の場合、これは通常gettimeofday
システムコールにマッピングされます。
変更前は、time.now
関数内でgettimeofday
を呼び出す際に、特定の固定アドレス0xffffffffff600000
を直接使用していました。このアドレスは、LinuxカーネルがvDSOをマッピングする一般的なベースアドレスの一つですが、これはハードコードされた値であり、vDSOのシンボル解決を適切に行っているわけではありませんでした。
変更後は、runtime·__vdso_gettimeofday_sym(SB)
というシンボルを参照するように修正されています。これは、GoランタイムがvDSOのgettimeofday
関数のエントリポイントを動的に解決し、そのアドレスをAX
レジスタにロードしてからCALL
命令で呼び出すことを意味します。
vDSOによるgettimeofday
の動作は以下のようになります。
- ユーザー空間での実行:
gettimeofday
の呼び出しは、カーネルへのトラップ(システムコール)を発生させず、ユーザー空間にマッピングされたvDSOページ内のコードを実行します。 - 時刻データの取得: vDSO内のコードは、カーネルがユーザー空間に公開している共有メモリ領域から、現在の時刻に関する情報(例: タイムスタンプカウンタのオフセット、周波数など)を読み取ります。
rdtsc
の利用: 多くの場合、vDSOの時刻取得関数は、CPUのrdtsc
(Read Time-Stamp Counter)命令を利用します。rdtsc
は、CPUが起動してからのサイクル数を非常に高速に取得できる命令です。vDSOは、このrdtsc
の値と、カーネルから提供されるオフセットや周波数情報を用いて、現在の壁時計時刻を計算します。- コンテキストスイッチの回避: このプロセス全体がユーザー空間で完結するため、カーネルモードへのコンテキストスイッチが不要となり、大幅なパフォーマンス向上が実現されます。
この変更により、GoプログラムはLinux/amd64環境において、より効率的に時刻情報を取得できるようになり、特に高負荷な状況下でのパフォーマンスが改善されます。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/sys_linux_amd64.s
ファイル内のtime·now
関数の実装にあります。
--- a/src/pkg/runtime/sys_linux_amd64.s
+++ b/src/pkg/runtime/sys_linux_amd64.s
@@ -104,7 +104,7 @@ TEXT runtime·mincore(SB),7,$0-24
TEXT time·now(SB), 7, $32
LEAQ 8(SP), DI
MOVQ $0, SI
- MOVQ $0xffffffffff600000, AX
+ MOVQ runtime·__vdso_gettimeofday_sym(SB), AX
CALL AX
MOVQ 8(SP), AX // sec
MOVL 16(SP), DX // usec
具体的には、107行目のMOVQ
命令が変更されています。
- 変更前:
MOVQ $0xffffffffff600000, AX
- 変更後:
MOVQ runtime·__vdso_gettimeofday_sym(SB), AX
コアとなるコードの解説
このアセンブリコードスニペットは、Goランタイムのtime.now
関数の一部であり、Linux/amd64アーキテクチャにおける時刻取得ロジックを実装しています。
-
LEAQ 8(SP), DI
:SP
はスタックポインタレジスタです。8(SP)
は、スタックポインタから8バイトオフセットしたアドレスを指します。LEAQ
(Load Effective Address Quadword) 命令は、オペランドのアドレスを計算し、その結果をDI
レジスタにロードします。- これは、
gettimeofday
システムコール(またはvDSO関数)の最初の引数(tv
構造体へのポインタ)を準備していると考えられます。tv
構造体は、秒とマイクロ秒を格納するために使用されます。
-
MOVQ $0, SI
:MOVQ
(Move Quadword) 命令は、即値0
をSI
レジスタにロードします。- これは、
gettimeofday
システムコールの2番目の引数(tz
構造体へのポインタ、タイムゾーン情報)をNULL
に設定していると考えられます。
-
変更された行:
- 変更前 (
MOVQ $0xffffffffff600000, AX
):0xffffffffff600000
は、LinuxカーネルがvDSOをマッピングする一般的な仮想アドレスの一つです。- この命令は、
gettimeofday
関数のアドレスがこの固定値であると仮定し、そのアドレスをAX
レジスタに直接ロードしていました。これは、vDSOのシンボル解決を動的に行わず、ハードコードされたアドレスに依存するアプローチでした。この方法は、カーネルのバージョンや設定によってはvDSOのアドレスが異なる場合に問題を引き起こす可能性がありました。
- 変更後 (
MOVQ runtime·__vdso_gettimeofday_sym(SB), AX
):runtime·__vdso_gettimeofday_sym(SB)
は、Goリンカが解決するシンボルです。Goランタイムは、プログラムの起動時にvDSOのシンボルテーブルを解析し、__vdso_gettimeofday
関数の実際のアドレスを特定します。- この命令は、特定された
__vdso_gettimeofday
関数のアドレスをAX
レジスタにロードします。これにより、ハードコードされたアドレスに依存することなく、動的に解決された正確なvDSO関数のエントリポイントを呼び出すことが可能になります。
- 変更前 (
-
CALL AX
:AX
レジスタにロードされたアドレス(vDSOのgettimeofday
関数のエントリポイント)に対して関数呼び出しを行います。これにより、ユーザー空間内で高速に時刻情報が取得されます。
-
MOVQ 8(SP), AX // sec
とMOVL 16(SP), DX // usec
:CALL AX
の実行後、gettimeofday
関数は結果をスタック上のtv
構造体に書き込みます。- これらの命令は、スタックから秒(
sec
)とマイクロ秒(usec
)の値を取得し、それぞれAX
とDX
レジスタにロードしています。これらの値は、Goのtime.now
関数の戻り値として使用されます。
この変更により、GoランタイムはLinux/amd64システムにおいて、より堅牢かつパフォーマンスの高い方法でvDSOを利用し、時刻取得の効率を大幅に向上させています。
関連リンク
- Go CL (Code Review) 6826072: https://golang.org/cl/6826072
参考にした情報源リンク
- vDSO (virtual Dynamic Shared Object) の概要:
- vDSOと
gettimeofday
のパフォーマンス: - Linux vDSO on x86_64: https://lwn.net/Articles/446528/
rdtsc
命令: https://en.wikipedia.org/wiki/Time-stamp_countergettimeofday
man page:man 2 gettimeofday
(Linuxシステム上で実行)