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

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

このコミットは、Go言語のランタイムにおけるARMアーキテクチャ向けのメモリ操作関数に関する変更です。具体的には、memsetmemclrという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.smemclr_arm.sにリネームし、asm_arm.sから同名の関数をマージすることで、既存のmemclrの仲介層を解消するというものです。

変更の背景

Go言語のランタイムは、ガベージコレクションやメモリ割り当てなど、低レベルのメモリ管理を効率的に行う必要があります。memsetは指定されたメモリ領域を特定の値で埋める汎用的な関数であり、memclr(またはbzero)はメモリ領域をゼロで埋める特殊な関数です。

このコミット以前は、ARMアーキテクチャにおいて、runtime·memclr関数がruntime·memset関数を呼び出す形で実装されていました。これは、memclrmemsetの特殊なケース(値をゼロに設定する)であるため、コードの再利用という観点からは理解できます。しかし、この「シム」構造は、memclrが直接ゼロクリア操作を行うのではなく、一度memsetを介するというオーバーヘッドを生じさせていました。

この変更の背景には、Goランタイムのパフォーマンス最適化とコードの簡素化があります。特に、メモリクリア操作はガベージコレクションや新しいオブジェクトの初期化において頻繁に発生するため、その効率性はランタイム全体のパフォーマンスに大きく影響します。冗長な呼び出しを排除し、memclrを直接的なゼロクリア操作として実装することで、より高速な実行が期待されます。コミットメッセージにあるhttps://golang.org/cl/6300043/#msg3は、この変更に至る議論や提案の経緯を示している可能性があります。

前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

  1. Go言語のランタイム (runtime): Go言語のプログラムは、Goランタイム上で動作します。ランタイムは、ガベージコレクション、ゴルーチンのスケジューリング、メモリ管理、システムコールなど、プログラムの実行に必要な低レベルの機能を提供します。src/pkg/runtimeディレクトリには、これらの機能の多くが実装されています。

  2. アセンブリ言語 (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の略で、関数から戻ります。
  3. memsetmemclr (または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)と等価ですが、ゼロクリアに特化しているため、多くの場合より効率的な実装が可能です。
  4. 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.ssrc/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)に設定している点です。これは、メモリをゼロで埋めるための値としてゼロを使用することを明確に示しています。これにより、memclrmemsetを呼び出すことなく、直接ゼロクリア操作を実行できるようになります。

この変更により、memclrの呼び出しパスが短縮され、不要な関数呼び出しのオーバーヘッドが削減されます。特に、Goのガベージコレクタが頻繁にメモリをクリアする操作を行うため、この最適化はランタイム全体のパフォーマンス向上に寄与します。

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

このコミットによる主要なコード変更は以下の2つのファイルに集中しています。

  1. src/pkg/runtime/asm_arm.s:

    • runtime·memclr(SB)という名前の関数が完全に削除されました。この関数は、以前はruntime·memset(SB)を呼び出すシムとして機能していました。
  2. 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 */
 

この差分は、以下の重要な変更を示しています。

  1. ファイルのリネーム: memset_arm.smemclr_arm.sにリネームされました。これは、このファイルがmemsetではなくmemclrの機能を提供するようになったことを明確に示しています。
  2. 関数名の変更: TEXT runtime·memset(SB)TEXT runtime·memclr(SB),7,$0に変更されました。これにより、このアセンブリコードがmemclr関数としてエクスポートされるようになります。
  3. 引数の変更とゼロ値の明示:
    • 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のコードレビューシステム (Gerrit) のリンク:
    • https://golang.org/cl/6300043/#msg3
    • https://golang.org/cl/6336054 (これらのリンクは、当時のGoのコードレビューシステムへのリンクであり、現在はGoのGerritインスタンスにリダイレクトされる可能性があります。当時の議論の詳細を追うには、これらのCL番号でGerritを検索する必要があります。)
  • ARMアセンブリ言語の基本に関するドキュメント (一般的な情報源):
    • ARM Architecture Reference Manual (ARM社の公式ドキュメント)
    • オンラインのARMアセンブリチュートリアルやリファレンス
  • memsetmemclr/bzeroに関するC言語の標準ライブラリ関数ドキュメント (一般的な情報源):
    • manページ (man memset, man bzero)
    • C標準ライブラリのリファレンス