[インデックス 13385] ファイルの概要
このコミットは、Go言語のランタイムにおけるARMアーキテクチャ向けのメモリ操作関数に関する変更です。具体的には、memset
とmemclr
という2つのメモリ操作関数の実装方法を最適化し、冗長な「シム」(shim: 仲介層)を削除することを目的としています。これにより、ARM環境でのメモリクリア処理がより直接的かつ効率的になります。
コミット
commit 3a50bc1a246418f5983c13fe76799e918d03779d
Author: Dave Cheney <dave@cheney.net>
Date: Mon Jun 25 23:01:34 2012 +1000
runtime: remove memset/memclr shim
This CL resolves https://golang.org/cl/6300043/#msg3
by renaming memset_arm.s to memclr_arm.s and merging the function
of the same name from asm_arm.s.
R=minux.ma, rsc
CC=golang-dev
https://golang.org/cl/6336054
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3a50bc1a246418f5983c13fe76799e918d03779d
元コミット内容
このコミットの目的は、「runtime: remove memset/memclr shim」(ランタイム: memset/memclrシムの削除)です。これは、memset_arm.s
をmemclr_arm.s
にリネームし、asm_arm.s
から同名の関数をマージすることで、既存のmemclr
の仲介層を解消するというものです。
変更の背景
Go言語のランタイムは、ガベージコレクションやメモリ割り当てなど、低レベルのメモリ管理を効率的に行う必要があります。memset
は指定されたメモリ領域を特定の値で埋める汎用的な関数であり、memclr
(またはbzero
)はメモリ領域をゼロで埋める特殊な関数です。
このコミット以前は、ARMアーキテクチャにおいて、runtime·memclr
関数がruntime·memset
関数を呼び出す形で実装されていました。これは、memclr
がmemset
の特殊なケース(値をゼロに設定する)であるため、コードの再利用という観点からは理解できます。しかし、この「シム」構造は、memclr
が直接ゼロクリア操作を行うのではなく、一度memset
を介するというオーバーヘッドを生じさせていました。
この変更の背景には、Goランタイムのパフォーマンス最適化とコードの簡素化があります。特に、メモリクリア操作はガベージコレクションや新しいオブジェクトの初期化において頻繁に発生するため、その効率性はランタイム全体のパフォーマンスに大きく影響します。冗長な呼び出しを排除し、memclr
を直接的なゼロクリア操作として実装することで、より高速な実行が期待されます。コミットメッセージにあるhttps://golang.org/cl/6300043/#msg3
は、この変更に至る議論や提案の経緯を示している可能性があります。
前提知識の解説
このコミットを理解するためには、以下の前提知識が必要です。
-
Go言語のランタイム (runtime): Go言語のプログラムは、Goランタイム上で動作します。ランタイムは、ガベージコレクション、ゴルーチンのスケジューリング、メモリ管理、システムコールなど、プログラムの実行に必要な低レベルの機能を提供します。
src/pkg/runtime
ディレクトリには、これらの機能の多くが実装されています。 -
アセンブリ言語 (ARM): Goランタイムの一部は、パフォーマンスが重要な部分やハードウェアに直接アクセスする必要がある部分でアセンブリ言語で記述されています。このコミットでは、ARMアーキテクチャ向けのアセンブリコード(
.s
ファイル)が変更されています。ARMアセンブリの基本的な命令(MOVW
,ADD
,RET
など)やレジスタ(R0
,FP
など)、シンボル(SB
,TEXT
など)の知識が役立ちます。TEXT
: 関数の開始を宣言します。SB
: Static Baseの略で、グローバルシンボルや外部シンボルを参照する際に使われます。FP
: Frame Pointerの略で、スタックフレーム内の引数やローカル変数にアクセスするために使われます。0(FP)
は最初の引数、4(FP)
は2番目の引数といった形で参照されます。R0
,R1
など: ARMプロセッサの汎用レジスタです。MOVW
: Move Wordの略で、32ビットの値を移動する命令です。ADD
: 加算命令です。BL
: Branch with Linkの略で、関数呼び出しを行います。呼び出し元のリターンアドレスをリンクレジスタ(LR)に保存します。RET
: Returnの略で、関数から戻ります。
-
memset
とmemclr
(またはbzero
):memset(void *s, int c, size_t n)
: メモリブロックs
の最初のn
バイトを、指定された文字c
(バイト値)で埋めます。memclr(void *s, size_t n)
(またはbzero(void *s, size_t n)
): メモリブロックs
の最初のn
バイトをゼロで埋めます。これはmemset(s, 0, n)
と等価ですが、ゼロクリアに特化しているため、多くの場合より効率的な実装が可能です。
-
GoのCalling Convention: Goのアセンブリ関数では、引数はスタックフレームの
FP
レジスタからのオフセットでアクセスされます。例えば、0(FP)
は最初の引数、4(FP)
は2番目の引数(32ビットシステムの場合)といった具合です。戻り値も同様に特定のオフセットに配置されます。
技術的詳細
このコミットの技術的な核心は、ARMアーキテクチャにおけるmemclr
の実装を、memset
を介する間接的な方法から、直接ゼロを書き込む方法へと変更した点にあります。
変更前は、src/pkg/runtime/asm_arm.s
に以下のようなruntime·memclr
のシムが存在していました。
TEXT runtime·memclr(SB),7,$0
MOVW 0(FP), R0 // 最初の引数 (ポインタ) を R0 にロード
MOVW $0, R1 // ゼロを R1 にロード
MOVW R1, 0(FP) // これは誤りか、または別の目的のコード。通常は引数を変更しない。
BL runtime·memset(SB) // memset を呼び出す
RET
このMOVW R1, 0(FP)
は、memset
の第2引数(埋める値)をスタック上の0(FP)
、つまりmemclr
の第1引数(ポインタ)の位置に書き込もうとしているように見えますが、これは通常のアセンブリの引数渡しとは異なる奇妙な挙動です。おそらく、これはmemset
の呼び出し規約に合わせた引数設定の一部だったか、あるいはバグの可能性もあります。いずれにせよ、このシムはmemset
を呼び出すことでmemclr
の機能を実現していました。
変更後は、このシムが削除され、代わりにsrc/pkg/runtime/memset_arm.s
がsrc/pkg/runtime/memclr_arm.s
にリネームされ、その内部のruntime·memset
関数がruntime·memclr
として再実装されました。
新しいruntime·memclr
の実装は以下のようになります。
TEXT runtime·memclr(SB),7,$0
MOVW ptr+0(FP), R(TO) // 最初の引数 'ptr' を R(TO) にロード
MOVW n+4(FP), R(N) // 2番目の引数 'n' を R(N) にロード
MOVW $0, R(0) // ゼロを R(0) にロード (埋める値)
ADD R(N), R(TO), R(TOE) /* to end pointer */
// ... (実際のゼロクリアループのコードが続く)
ここで重要なのは、MOVW $0, R(0)
という命令が追加され、明示的にゼロをレジスタR(0)
に設定している点です。これは、メモリをゼロで埋めるための値としてゼロを使用することを明確に示しています。これにより、memclr
はmemset
を呼び出すことなく、直接ゼロクリア操作を実行できるようになります。
この変更により、memclr
の呼び出しパスが短縮され、不要な関数呼び出しのオーバーヘッドが削減されます。特に、Goのガベージコレクタが頻繁にメモリをクリアする操作を行うため、この最適化はランタイム全体のパフォーマンス向上に寄与します。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下の2つのファイルに集中しています。
-
src/pkg/runtime/asm_arm.s
:runtime·memclr(SB)
という名前の関数が完全に削除されました。この関数は、以前はruntime·memset(SB)
を呼び出すシムとして機能していました。
-
src/pkg/runtime/{memset_arm.s => memclr_arm.s}
:- ファイル名が
memset_arm.s
からmemclr_arm.s
へと変更されました。 - ファイル内の
TEXT runtime·memset(SB)
という関数定義がTEXT runtime·memclr(SB)
へと変更されました。 - 関数の引数の取り込み方が変更され、特にゼロを埋める値として明示的に
$0
が設定されるようになりました。
- ファイル名が
コアとなるコードの解説
src/pkg/runtime/asm_arm.s
からの削除
--- a/src/pkg/runtime/asm_arm.s
+++ b/src/pkg/runtime/asm_arm.s
@@ -345,13 +345,6 @@ TEXT runtime·cgocallback(SB),7,$16
// Done!
RET
-TEXT runtime·memclr(SB),7,$0
- MOVW 0(FP), R0
- MOVW $0, R1
- MOVW R1, 0(FP)
- BL runtime·memset(SB)
- RET
-
TEXT runtime·getcallerpc(SB),7,$-4
MOVW 0(SP), R0
RET
この差分は、runtime·memclr
というアセンブリ関数がasm_arm.s
から削除されたことを示しています。この関数は、以前はmemset
を呼び出すことでメモリクリアを実現していましたが、このコミットによってその役割は新しいmemclr_arm.s
に直接実装されることになります。これにより、間接的な呼び出しが解消され、コードパスが簡素化されます。
src/pkg/runtime/{memset_arm.s => memclr_arm.s}
の変更
--- a/src/pkg/runtime/memset_arm.s
+++ b/src/pkg/runtime/memclr_arm.s
similarity index 96%
rename from src/pkg/runtime/memset_arm.s
rename to src/pkg/runtime/memclr_arm.s
index 8bc2004022..afc529d907 100644
--- a/src/pkg/runtime/memset_arm.s
+++ b/src/pkg/runtime/memclr_arm.s
@@ -28,10 +28,10 @@ TOE = 11
N = 12
TMP = 12 /* N and TMP don't overlap */
-TEXT runtime·memset(SB), $0
- MOVW R0, R(TO)
- MOVW data+4(FP), R(0)
- MOVW n+8(FP), R(N)
+TEXT runtime·memclr(SB),7,$0
+ MOVW ptr+0(FP), R(TO)
+ MOVW n+4(FP), R(N)
+ MOVW $0, R(0)
ADD R(N), R(TO), R(TOE) /* to end pointer */
この差分は、以下の重要な変更を示しています。
- ファイルのリネーム:
memset_arm.s
がmemclr_arm.s
にリネームされました。これは、このファイルがmemset
ではなくmemclr
の機能を提供するようになったことを明確に示しています。 - 関数名の変更:
TEXT runtime·memset(SB)
がTEXT runtime·memclr(SB),7,$0
に変更されました。これにより、このアセンブリコードがmemclr
関数としてエクスポートされるようになります。 - 引数の変更とゼロ値の明示:
MOVW R0, R(TO)
がMOVW ptr+0(FP), R(TO)
に変更されました。これは、メモリをクリアする対象のポインタ(ptr
)を最初の引数としてR(TO)
レジスタにロードすることを示しています。MOVW data+4(FP), R(0)
が削除されました。これは、以前のmemset
が埋める値(data
)を引数として受け取っていた名残です。MOVW n+8(FP), R(N)
がMOVW n+4(FP), R(N)
に変更されました。これは、クリアするバイト数(n
)を2番目の引数としてR(N)
レジスタにロードすることを示しています。オフセットが8(FP)
から4(FP)
に変わったのは、引数の数が減った(data
引数がなくなった)ため、n
のスタック上の位置が変わったことを意味します。MOVW $0, R(0)
が追加されました。 これが最も重要な変更点です。この命令は、メモリを埋める値として明示的にゼロ($0
)をR(0)
レジスタに設定しています。これにより、このアセンブリ関数はmemset
の汎用的な機能ではなく、memclr
の特定の機能(ゼロクリア)を直接実行するようになります。
これらの変更により、GoランタイムはARMアーキテクチャにおいて、memclr
操作をより効率的かつ直接的に実行できるようになりました。
関連リンク
- Go言語のソースコードリポジトリ: https://github.com/golang/go
- Go言語のランタイムパッケージ: https://pkg.go.dev/runtime
- GoのIssueトラッカー (関連するCLの議論が見つかる可能性): https://go.dev/issue
参考にした情報源リンク
- コミットメッセージに記載されているGoのコードレビューシステム (Gerrit) のリンク:
https://golang.org/cl/6300043/#msg3
https://golang.org/cl/6336054
(これらのリンクは、当時のGoのコードレビューシステムへのリンクであり、現在はGoのGerritインスタンスにリダイレクトされる可能性があります。当時の議論の詳細を追うには、これらのCL番号でGerritを検索する必要があります。)
- ARMアセンブリ言語の基本に関するドキュメント (一般的な情報源):
- ARM Architecture Reference Manual (ARM社の公式ドキュメント)
- オンラインのARMアセンブリチュートリアルやリファレンス
memset
とmemclr
/bzero
に関するC言語の標準ライブラリ関数ドキュメント (一般的な情報源):- manページ (
man memset
,man bzero
) - C標準ライブラリのリファレンス
- manページ (