[インデックス 18999] ファイルの概要
このコミットは、Goランタイムにおけるメモリのコピーとゼロクリア処理のパフォーマンス改善を目的としています。特に、x86アーキテクチャのREP MOVSQ
およびREP STOSQ
命令が持つ起動オーバーヘッドの問題を回避するため、Duff's Device(ダフのデバイス)と呼ばれる最適化手法を導入しています。これにより、比較的小さなメモリブロックの操作において顕著な速度向上が見られます。
コミット
commit 6c7cbf086c34ebb88311ba12d3a75adcbdce8ac8
Author: Keith Randall <khr@golang.org>
Date: Tue Apr 1 12:51:02 2014 -0700
runtime: get rid of most uses of REP for copying/zeroing.
REP MOVSQ and REP STOSQ have a really high startup overhead.
Use a Duff's device to do the repetition instead.
benchmark old ns/op new ns/op delta
BenchmarkClearFat32 7.20 1.60 -77.78%
BenchmarkCopyFat32 6.88 2.38 -65.41%
BenchmarkClearFat64 7.15 3.20 -55.24%
BenchmarkCopyFat64 6.88 3.44 -50.00%
BenchmarkClearFat128 9.53 5.34 -43.97%
BenchmarkCopyFat128 9.27 5.56 -40.02%
BenchmarkClearFat256 13.8 9.53 -30.94%
BenchmarkCopy256 13.5 10.3 -23.70%
BenchmarkClearFat512 22.3 18.0 -19.28%
BenchmarkCopyFat512 22.0 19.7 -10.45%
BenchmarkCopyFat1024 36.5 38.4 +5.21%
BenchmarkClearFat1024 35.1 35.0 -0.28%
TODO: use for stack frame zeroing
TODO: REP prefixes are still used for "reverse" copying when src/dst
regions overlap. Might be worth fixing.
LGTM=rsc
R=golang-codereviews, rsc
CC=golang-codereviews, r
https://golang.org/cl/81370046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6c7cbf086c34ebb88311ba12d3a75adcbdce8ac8
元コミット内容
このコミットの目的は、Goランタイムにおけるメモリのコピー(MOVSQ
)とゼロクリア(STOSQ
)処理において、x86のREP
プレフィックスの使用をほとんど排除することです。REP
プレフィックスは、繰り返し操作を行うための命令ですが、その起動オーバーヘッドが非常に高いため、特に小さなデータサイズでのパフォーマンスが低下していました。この問題を解決するために、Duff's Deviceという手法を用いて繰り返し処理を行うように変更されました。
ベンチマーク結果は、特に小さなデータサイズ(32バイトから512バイト)において、ゼロクリアとコピーの両方で大幅なパフォーマンス改善を示しています。例えば、BenchmarkClearFat32
では77.78%の改善、BenchmarkCopyFat32
では65.41%の改善が見られます。しかし、1024バイトのような大きなサイズでは、改善効果は限定的か、わずかに性能が低下するケースもあります。
コミットメッセージには、今後の改善点としてスタックフレームのゼロクリアへの適用や、ソースとデスティネーション領域がオーバーラップする場合のリバースコピーにおけるREP
プレフィックスの使用の修正が挙げられています。
変更の背景
Goランタイムは、メモリの確保や解放、ガベージコレクション(GC)の過程で頻繁にメモリのゼロクリアやコピーを行います。これらの操作は、プログラム全体のパフォーマンスに大きな影響を与えます。
従来のGoランタイムでは、x86アーキテクチャにおいて、大量のメモリを効率的に操作するためにREP
プレフィックス(REP MOVSQ
やREP STOSQ
など)が使用されていました。これらの命令は、CPUのマイクロコードによって最適化されており、理論的には非常に高いスループットを発揮します。しかし、実際のCPUの挙動には、命令の起動にかかる「起動オーバーヘッド」が存在します。
この起動オーバーヘッドは、特にコピーやゼロクリアの対象となるメモリブロックが小さい場合に顕著な問題となります。例えば、数十バイトから数百バイト程度の小さなオブジェクトの操作では、REP
命令の起動オーバーヘッドが実際のデータ転送にかかる時間よりも大きくなり、結果としてパフォーマンスが低下していました。Goのプログラムでは、小さなオブジェクトの割り当てや操作が頻繁に行われるため、このオーバーヘッドが無視できない問題となっていました。
このコミットは、この「小さなサイズでのREP
命令の非効率性」という具体的なパフォーマンスボトルネックを解消するために行われました。
前提知識の解説
x86のREP
プレフィックスと文字列命令
x86アーキテクチャには、メモリブロックを操作するための「文字列命令(String Instructions)」と呼ばれる特殊な命令群があります。これらは、MOVS
(Move String)、STOS
(Store String)、CMPS
(Compare String)などです。これらの命令は、通常、SI
(Source Index)レジスタとDI
(Destination Index)レジスタをそれぞれソースとデスティネーションのアドレスとして使用し、CX
(Count)レジスタを操作回数として使用します。
REP
プレフィックスは、これらの文字列命令と組み合わせて使用することで、CX
レジスタがゼロになるまで命令を繰り返し実行する機能を提供します。
REP MOVSQ
:Q
はQuadword(8バイト)を意味します。SI
が指すアドレスからDI
が指すアドレスへ8バイト単位でデータをコピーし、SI
とDI
をインクリメント(またはデクリメント、DFフラグによる)し、CX
をデクリメントします。CX
がゼロになるまで繰り返します。REP STOSQ
:Q
はQuadword(8バイト)を意味します。AL/AX/EAX/RAX
レジスタの内容をDI
が指すアドレスへ8バイト単位でストアし、DI
をインクリメント(またはデクリメント)し、CX
をデクリメントします。CX
がゼロになるまで繰り返します。
パフォーマンス特性:
現代のCPUでは、これらのREP
命令はマイクロコードによって実装されており、非常に最適化されています。特に大きなデータブロック(数百バイト以上)の転送においては、高いスループットを発揮し、メモリ帯域幅に近い性能を達成できます。しかし、前述の通り、命令の実行を開始するための「起動オーバーヘッド」が存在します。このオーバーヘッドは、命令のデコード、マイクロコードのロード、内部パイプラインのセットアップなどにかかる時間であり、データサイズが小さい場合には、この固定コストが相対的に大きくなり、結果として非効率になります。
Duff's Device(ダフのデバイス)
Duff's Deviceは、1983年にトム・ダフが開発したC言語のプログラミングテクニックで、ループアンローリング(Loop Unrolling)を手動で実装する非常に巧妙な方法です。これは、switch
文のフォールスルー(fall-through)特性とdo-while
ループを組み合わせることで実現されます。
動作原理:
通常のループアンローリングでは、ループの本体を複数回記述することで、ループの条件判定やカウンタの更新といったオーバーヘッドを減らします。しかし、ループの反復回数がアンローリングの倍数でない場合、残りの処理をどうするかという問題が生じます。Duff's Deviceは、この「残りの処理」をswitch
文のフォールスルーを利用して、メインのアンローリングされたループの途中にジャンプすることで処理します。
例えば、8回アンローリングする場合、count % 8
の結果に応じてswitch
文が適切なcase
ラベルにジャンプします。case
ラベルはdo-while
ループの内部に配置されており、フォールスルーによって残りの処理が実行された後、通常のアンローリングされたループが続行されます。
アセンブリレベルでの最適化:
Duff's Deviceがコンパイルされると、switch
文は通常、ジャンプテーブル(jump table)に変換されます。これにより、計算されたインデックスに基づいて直接目的のコードブロックにジャンプでき、連続したif-else
文よりも効率的です。ループのオーバーヘッド(条件分岐とカウンタのデクリメント)が大幅に削減されるため、CPUのパイプライン効率が向上し、より多くの命令を連続して実行できるようになります。
現代におけるDuff's Device: 現代のコンパイラは非常に高度であり、自動的にループアンローリングやその他の最適化を適用できます。多くの場合、手動でDuff's Deviceを使用するよりも、コンパイラに任せた方が良い結果が得られます。また、Duff's Deviceはコードの可読性を著しく損なうため、特殊なパフォーマンス要件がない限り、一般的には推奨されません。しかし、このコミットのように、特定の低レベルなランタイムコードやアセンブリレベルでの最適化が必要な場面では、その原理が応用されることがあります。
Goランタイムのメモリ管理
Go言語は、自動ガベージコレクション(GC)を備えており、開発者が手動でメモリを管理する必要がありません。Goランタイムは、メモリの割り当て、ゼロクリア、コピー、解放を効率的に行います。
- 自動ゼロクリア: Goでは、新しく割り当てられたメモリはデフォルトでゼロ値に初期化されます(例: 整数型は
0
、ポインタはnil
、文字列は""
、ブール型はfalse
)。これは、未初期化メモリの使用によるバグやセキュリティ脆弱性を防ぐための安全機能です。 - メモリコピーの最適化: Goランタイムは、パフォーマンスを向上させ、GCの負荷を軽減するために、不要なメモリコピーを最小限に抑えるよう努めます。例えば、スライス間のコピーには組み込みの
copy()
関数が最も効率的です。また、大きな配列を値渡しするとコピーが発生するため、ポインタ渡しが推奨されることがあります。
このコミットは、Goランタイムが内部的に行うこれらのメモリ操作の効率を、アセンブリレベルでさらに高めることを目的としています。
技術的詳細
このコミットの主要な変更は、Goコンパイラ(6g
、8g
はそれぞれamd64と386アーキテクチャ向けのコンパイラ)とランタイムのアセンブリコードにDuff's Deviceの概念を導入し、REP
プレフィックスの使用を置き換えることです。
具体的には、以下の新しいアセンブリ命令が導入されました。
ADUFFCOPY
: メモリコピーのためのDuff's Deviceを呼び出す命令。ADUFFZERO
: メモリゼロクリアのためのDuff's Deviceを呼び出す命令。
これらの命令は、Goコンパイラによって生成されるコード内で、特定のサイズのメモリブロック(コミットメッセージのベンチマーク結果から、約128バイト以下の比較的小さなサイズ)のコピーやゼロクリアが必要な場合に選択的に使用されます。
src/cmd/6g/cgen.c
および src/cmd/8g/cgen.c
(sgen
関数)
これらのファイルは、Goコンパイラのコード生成部分であり、構造体や配列などの「fat object」のコピー処理を担当するsgen
関数が変更されています。
変更前は、コピーサイズq
が4クアッドワード(32バイト)以上の場合にREP MOVSQ
(amd64の場合)またはREP MOVSL
(386の場合)を使用していました。
変更後は、q
が128クアッドワード(amd64で1024バイト、386で512バイト)を超える場合にのみREP
命令を使用し、q
が4クアッドワード以上128クアッドワード以下の場合は、新しく導入されたADUFFCOPY
命令を使用するように変更されています。
ADUFFCOPY
命令は、duffcopy
というランタイム関数へのジャンプ命令として生成されます。このジャンプ命令のオフセットは、コピーするバイト数q
に基づいて計算されます。このオフセットがDuff's Deviceの「マジック定数」と組み合わされ、duffcopy
ルーチン内の適切な開始点にジャンプすることで、ループアンローリングされたコピー処理が効率的に実行されます。
src/cmd/6g/ggen.c
および src/cmd/8g/ggen.c
(clearfat
関数)
これらのファイルは、Goコンパイラのコード生成部分であり、メモリのゼロクリア処理を担当するclearfat
関数が変更されています。
変更前は、ゼロクリアサイズq
が4クアッドワード(32バイト)以上の場合にREP STOSQ
(amd64の場合)またはREP STOSL
(386の場合)を使用していました。
変更後は、q
が128クアッドワードを超える場合にのみREP
命令を使用し、q
が4クアッドワード以上128クアッドワード以下の場合は、新しく導入されたADUFFZERO
命令を使用するように変更されています。
ADUFFZERO
命令は、duffzero
というランタイム関数へのジャンプ命令として生成されます。ADUFFCOPY
と同様に、ゼロクリアするバイト数q
に基づいてオフセットが計算され、duffzero
ルーチン内の適切な開始点にジャンプすることで、ループアンローリングされたゼロクリア処理が効率的に実行されます。
src/cmd/6l/6.out.h
および src/cmd/8l/8.out.h
これらのヘッダーファイルは、Goリンカが使用する命令コードの定義を含んでいます。新しく導入されたADUFFCOPY
とADUFFZERO
命令が、リンカが認識できるように列挙型に追加されています。
src/liblink/asm6.c
および src/liblink/asm8.c
これらのファイルは、Goのアセンブラとリンカがアセンブリ命令を処理する方法を定義しています。ADUFFCOPY
とADUFFZERO
命令が、リンカの命令テーブルに追加され、それらがyduff
という新しいオペランドタイプ(即値32ビット)を持つZcall
命令として扱われるように設定されています。特に注目すべきは、リロケーション処理において、p->to.offset
(Duff's Deviceのジャンプオフセット)がr->add
としてリロケーションエントリに追加されている点です。これにより、リンカは実行時に正確なジャンプ先アドレスを解決できます。
src/pkg/runtime/asm_386.s
および src/pkg/runtime/asm_amd64.s
これらのファイルは、Goランタイムの低レベルアセンブリコードを含んでいます。このコミットの核心部分であり、runtime·duffzero
とruntime·duffcopy
という2つの新しいアセンブリ関数が実装されています。
runtime·duffzero
(ゼロクリア用Duff's Device)
- 386版 (
asm_386.s
):STOSL
(Store String Longword、4バイト)命令を128回繰り返す形で記述されています。AX
レジスタにはゼロが設定され、DI
レジスタが指すメモリを4バイトずつゼロクリアしていきます。コンパイラは、ゼロクリアするバイト数に応じて、このルーチン内の適切なSTOSL
命令の開始点にジャンプします。例えば、32バイト(8回STOSL
)ゼロクリアしたい場合、STOSL
命令が8回実行されるようにジャンプオフセットが計算されます。 - amd64版 (
asm_amd64.s
):STOSQ
(Store String Quadword、8バイト)命令を128回繰り返す形で記述されています。AX
レジスタにはゼロが設定され、DI
レジスタが指すメモリを8バイトずつゼロクリアしていきます。同様に、コンパイラはゼロクリアするバイト数に応じて適切な開始点にジャンプします。
runtime·duffcopy
(コピー用Duff's Device)
- 386版 (
asm_386.s
):MOVL (SI),CX; ADDL $4,SI; MOVL CX,(DI); ADDL $4,DI
という4バイトコピーのシーケンスを128回繰り返す形で記述されています。SI
レジスタがソース、DI
レジスタがデスティネーションを指します。CX
レジスタは一時的なデータ転送に使用されます。コンパイラは、コピーするバイト数に応じて、このルーチン内の適切なコピーシーケンスの開始点にジャンプします。 - amd64版 (
asm_amd64.s
):MOVQ (SI),CX; ADDQ $8,SI; MOVQ CX,(DI); ADDQ $8,DI
という8バイトコピーのシーケンスを128回繰り返す形で記述されています。SI
レジスタがソース、DI
レジスタがデスティネーションを指します。CX
レジスタは一時的なデータ転送に使用されます。同様に、コンパイラはコピーするバイト数に応じて適切な開始点にジャンプします。
注目すべきは、amd64版のduffcopy
ルーチンに「NOTE: this is equivalent to a sequence of MOVSQ but for some reason that is 3.5x slower than this code. The STOSQ above seem fine, though.
」というコメントがある点です。これは、MOVSQ
命令の単体実行が、MOVQ
とADDQ
を組み合わせた手動のコピーシーケンスよりも遅いという、当時のCPUのマイクロアーキテクチャに起因する興味深いパフォーマンス特性を示唆しています。一方で、STOSQ
は問題なく機能していると述べられています。
src/pkg/runtime/memmove_test.go
このファイルには、新しいベンチマークテストが追加されています。BenchmarkClearFatXX
とBenchmarkCopyFatXX
という形式で、様々なサイズの「fat object」(大きな構造体や配列)のゼロクリアとコピーのパフォーマンスを測定するためのテストです。これらのテストは、コミットメッセージに記載されているベンチマーク結果を生成するために使用されました。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、Goコンパイラがメモリのコピーとゼロクリアを行う際に、REP
プレフィックス付きの命令を使用する条件を変更し、代わりにDuff's Deviceを呼び出す新しいアセンブリ命令(ADUFFCOPY
, ADUFFZERO
)を導入した点です。そして、その新しい命令の実体として、Duff's Deviceの原理に基づいたアセンブリルーチン(runtime·duffzero
, runtime·duffcopy
)がランタイムに追加されました。
具体的な変更箇所は以下のファイルに集中しています。
-
src/cmd/6g/cgen.c
(amd64コンパイラ):sgen
関数内で、コピーサイズq
が4
以上128
以下の範囲でADUFFCOPY
を使用するように変更。--- a/src/cmd/6g/cgen.c +++ b/src/cmd/6g/cgen.c @@ -1447,10 +1448,16 @@ sgen(Node *n, Node *ns, int64 w) } else { // normal direction - if(q >= 4) { + if(q > 128) { gconreg(movptr, q, D_CX); gins(AREP, N, N); // repeat gins(AMOVSQ, N, N); // MOVQ *(SI)+,*(DI)+ + } else if (q >= 4) { + p = gins(ADUFFCOPY, N, N); + p->to.type = D_ADDR; + p->to.sym = linksym(pkglookup("duffcopy", runtimepkg)); + // 14 and 128 = magic constants: see ../../pkg/runtime/asm_amd64.s + p->to.offset = 14*(128-q); } else while(q > 0) { gins(AMOVSQ, N, N); // MOVQ *(SI)+,*(DI)+
-
src/cmd/6g/ggen.c
(amd64コンパイラ):clearfat
関数内で、ゼロクリアサイズq
が4
以上128
以下の範囲でADUFFZERO
を使用するように変更。--- a/src/cmd/6g/ggen.c +++ b/src/cmd/6g/ggen.c @@ -1088,10 +1088,16 @@ clearfat(Node *nl) savex(D_AX, &ax, &oldax, N, types[tptr]); gconreg(AMOVL, 0, D_AX); - if(q >= 4) { + if(q > 128) { gconreg(movptr, q, D_CX); gins(AREP, N, N); // repeat gins(ASTOSQ, N, N); // STOQ AL,*(DI)+ + } else if(q >= 4) { + p = gins(ADUFFZERO, N, N); + p->to.type = D_ADDR; + p->to.sym = linksym(pkglookup("duffzero", runtimepkg)); + // 2 and 128 = magic constants: see ../../pkg/runtime/asm_amd64.s + p->to.offset = 2*(128-q); } else while(q > 0) { gins(ASTOSQ, N, N); // STOQ AL,*(DI)+
-
src/pkg/runtime/asm_386.s
:runtime·duffzero
とruntime·duffcopy
のアセンブリ実装を追加。runtime·duffzero
: 128個のSTOSL
命令のシーケンス。runtime·duffcopy
: 128個のMOVL (SI),CX; ADDL $4,SI; MOVL CX,(DI); ADDL $4,DI
シーケンス。
-
src/pkg/runtime/asm_amd64.s
:runtime·duffzero
とruntime·duffcopy
のアセンブリ実装を追加。runtime·duffzero
: 128個のSTOSQ
命令のシーケンス。runtime·duffcopy
: 128個のMOVQ (SI),CX; ADDQ $8,SI; MOVQ CX,(DI); ADDQ $8,DI
シーケンス。
(src/cmd/8g/cgen.c
, src/cmd/8g/ggen.c
, src/cmd/6g/prog.c
, src/cmd/8g/prog.c
, src/cmd/6l/6.out.h
, src/cmd/8l/8.out.h
, src/liblink/asm6.c
, src/liblink/asm8.c
も関連する変更が含まれますが、上記が主要なロジック変更点です。)
コアとなるコードの解説
コンパイラ側の変更 (cgen.c
, ggen.c
)
Goコンパイラは、メモリのコピーやゼロクリアが必要なGoのコード(例: 構造体の代入、配列の初期化)をアセンブリ命令に変換します。このコミットでは、その変換ロジックが変更されました。
変更前は、一定サイズ以上のメモリ操作には無条件にREP
プレフィックス付きの文字列命令(MOVSQ
/STOSQ
など)を生成していました。しかし、このコミットにより、メモリサイズq
に応じて以下の分岐ロジックが適用されます。
-
q > 128
(クアッドワード/ロングワード): この場合、メモリサイズが十分に大きいため、REP
プレフィックスの起動オーバーヘッドが相対的に小さくなります。したがって、引き続きREP
プレフィックス付きのMOVSQ
/STOSQ
命令が使用されます。これは、REP
命令が大規模なデータ転送において高いスループットを発揮するためです。 -
4 <= q <= 128
(クアッドワード/ロングワード): この範囲のメモリサイズは、REP
プレフィックスの起動オーバーヘッドが問題となる「中程度のサイズ」に該当します。ここで、コンパイラは新しい擬似命令であるADUFFCOPY
またはADUFFZERO
を生成します。gins(ADUFFCOPY, N, N)
:ADUFFCOPY
命令を生成します。p->to.sym = linksym(pkglookup("duffcopy", runtimepkg))
: この命令がruntime
パッケージ内のduffcopy
関数(アセンブリルーチン)を指すようにシンボルを設定します。p->to.offset = 14*(128-q)
(amd64cgen.c
の例): ここがDuff's Deviceの核心部分です。duffcopy
ルーチン内のどこにジャンプするかを決定するオフセットを計算します。14
や2
といった定数は、アセンブリルーチン内の各命令のバイトサイズと、Duff's Deviceのアンローリング単位(この場合は128)に基づいた「マジック定数」です。128-q
は、残りの処理回数に相当し、これによりduffcopy
ルーチン内の適切な開始点にジャンプし、必要な回数だけコピー/ゼロクリア操作が実行されます。
-
q < 4
(クアッドワード/ロングワード): この場合、メモリサイズが非常に小さいため、ループアンローリングの恩恵も小さく、単純な単一のMOVSQ
/STOSQ
命令を繰り返し実行する従来のwhile
ループが使用されます。これは、Duff's Deviceのセットアップコストすらもオーバーヘッドとなる可能性があるためです。
ランタイムアセンブリ側の変更 (asm_386.s
, asm_amd64.s
)
runtime·duffzero
とruntime·duffcopy
は、Duff's Deviceの原理に基づいて手動でループアンローリングされたアセンブリルーチンです。
-
runtime·duffzero
: このルーチンは、STOSL
(386)またはSTOSQ
(amd64)命令を128回連続して記述することで、メモリのゼロクリアをアンローリングしています。コンパイラが生成したADUFFZERO
命令は、このルーチン内の特定のSTOS
命令に直接ジャンプします。例えば、32バイト(amd64では4クアッドワード)をゼロクリアする場合、duffzero
ルーチンの末尾から4番目のSTOSQ
命令にジャンプし、そこからフォールスルーで4回のSTOSQ
が実行され、最後にRET
で戻ります。これにより、ループの条件判定やジャンプのオーバーヘッドなしに、必要な回数だけゼロクリアが実行されます。 -
runtime·duffcopy
: このルーチンは、MOV
とADD
命令のシーケンスを128回連続して記述することで、メモリコピーをアンローリングしています。MOVSQ
/MOVSL
命令の代わりに、MOVQ/L (SI),CX; ADDQ/L $8/4,SI; MOVQ/L CX,(DI); ADDQ/L $8/4,DI
というシーケンスを使用している点が重要です。これは、コミットメッセージのコメントにもあるように、当時のCPUではMOVSQ
単体よりもこの手動シーケンスの方が高速であったためです。duffzero
と同様に、コンパイラが生成したADUFFCOPY
命令は、このルーチン内の特定のコピーシーケンスに直接ジャンプし、必要な回数だけコピーが実行されます。
この変更により、特にGoランタイムが頻繁に操作する小さなオブジェクト(例: スタックフレームの初期化、小さな構造体のコピー)において、REP
命令の起動オーバーヘッドを回避し、より効率的なメモリ操作を実現しています。ベンチマーク結果が示すように、この最適化は顕著なパフォーマンス向上をもたらしました。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- このコミットのChange-ID:
81370046
(GoのコードレビューシステムGerritでのID)
参考にした情報源リンク
- x86
REP MOVSQ
andREP STOSQ
performance overhead:- https://stackoverflow.com/questions/11227809/why-is-memcpy-sometimes-faster-than-rep-movsb
- https://www.agner.org/optimize/optimizing_assembly.pdf (Agner Fog's optimization manuals)
- Duff's Device:
- Go runtime memory zeroing and copying:
- https://go.dev/doc/effective_go#allocation_new
- https://medium.com/a-journey-with-go/go-memory-management-part-1-the-basics-1b1e7222674a
- https://gobyexample.com/arrays
- https://gobyexample.com/slices
- https://go.dev/blog/go1.24-memory-optimizations (Go 1.24のメモリ最適化に関するブログ記事、関連情報として)
- https://go.dev/src/runtime/memmove_test.go (Goソースコード内の関連テストファイル)
- https://go.dev/src/runtime/asm_amd64.s (Goソースコード内の関連アセンブリファイル)
- https://go.dev/src/runtime/asm_386.s (Goソースコード内の関連アセンブリファイル)
- https://go.dev/src/cmd/6g/cgen.c (Goソースコード内の関連コンパイラファイル)
- https://go.dev/src/cmd/6g/ggen.c (Goソースコード内の関連コンパイラファイル)