[インデックス 19595] ファイルの概要
このコミットは、Goランタイムのamd64
アーキテクチャにおけるmemmove
(メモリコピー)関数のパフォーマンスを向上させるものです。特に、257バイトから約2KB(2048バイト)の中間サイズのメモリコピーにおいて、REP MOVSQ
命令の代わりにSSEレジスタを用いたMOV
命令を使用することで、処理速度を大幅に改善しています。
コミット
commit a712e20a1d1587e115c1295df0850120cdfa3e44
Author: Rui Ueyama <ruiu@google.com>
Date: Mon Jun 23 12:06:26 2014 -0700
runtime: speed up amd64 memmove
MOV with SSE registers seems faster than REP MOVSQ if the
size being copied is less than about 2K. Previously we
didn't use MOV if the memory region is larger than 256
byte. This patch improves the performance of 257 ~ 2048
byte non-overlapping copy by using MOV.
Here is the benchmark result on Intel Xeon 3.5GHz (Nehalem).
benchmark old ns/op new ns/op delta
BenchmarkMemmove16 4 4 +0.42%
BenchmarkMemmove32 5 5 -0.20%
BenchmarkMemmove64 6 6 -0.81%
BenchmarkMemmove128 7 7 -0.82%
BenchmarkMemmove256 10 10 +1.92%
BenchmarkMemmove512 29 16 -44.90%
BenchmarkMemmove1024 37 25 -31.55%
BenchmarkMemmove2048 55 44 -19.46%
BenchmarkMemmove4096 92 91 -0.76%
benchmark old MB/s new MB/s speedup
BenchmarkMemmove16 3370.61 3356.88 1.00x
BenchmarkMemmove32 6368.68 6386.99 1.00x
BenchmarkMemmove64 10367.37 10462.62 1.01x
BenchmarkMemmove128 17551.16 17713.48 1.01x
BenchmarkMemmove256 24692.81 24142.99 0.98x
BenchmarkMemmove512 17428.70 31687.72 1.82x
BenchmarkMemmove1024 27401.82 40009.45 1.46x
BenchmarkMemmove2048 36884.86 45766.98 1.24x
BenchmarkMemmove4096 44295.91 44627.86 1.01x
LGTM=khr
R=golang-codereviews, gobot, khr
CC=golang-codereviews
https://golang.org/cl/90500043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a712e20a1d1587e115c1295df0850120cdfa3e44
元コミット内容
runtime: speed up amd64 memmove
MOV with SSE registers seems faster than REP MOVSQ if the
size being copied is less than about 2K. Previously we
didn't use MOV if the memory region is larger than 256
byte. This patch improves the performance of 257 ~ 2048
byte non-overlapping copy by using MOV.
Here is the benchmark result on Intel Xeon 3.5GHz (Nehalem).
benchmark old ns/op new ns/op delta
BenchmarkMemmove16 4 4 +0.42%
BenchmarkMemmove32 5 5 -0.20%
BenchmarkMemmove64 6 6 -0.81%
BenchmarkMemmove128 7 7 -0.82%
BenchmarkMemmove256 10 10 +1.92%
BenchmarkMemmove512 29 16 -44.90%
BenchmarkMemmove1024 37 25 -31.55%
BenchmarkMemmove2048 55 44 -19.46%
BenchmarkMemmove4096 92 91 -0.76%
benchmark old MB/s new MB/s speedup
BenchmarkMemmove16 3370.61 3356.88 1.00x
BenchmarkMemmove32 6368.68 6386.99 1.00x
BenchmarkMemmove64 10367.37 10462.62 1.01x
BenchmarkMemmove128 17551.16 17713.48 1.01x
BenchmarkMemmove256 24692.81 24142.99 0.98x
BenchmarkMemmove512 17428.70 31687.72 1.82x
BenchmarkMemmove1024 27401.82 40009.45 1.46x
BenchmarkMemmove2048 36884.86 45766.98 1.24x
BenchmarkMemmove4096 44295.91 44627.86 1.01x
LGTM=khr
R=golang-codereviews, gobot, khr
CC=golang-codereviews
https://golang.org/cl/90500043
変更の背景
Goランタイムのmemmove
関数は、メモリブロックを効率的にコピーするために使用されます。amd64
アーキテクチャでは、この操作に主に2つのアプローチがあります。
REP MOVSQ
命令: これは、大量のデータを効率的にコピーするためのx86命令です。MOVSQ
は8バイト(クアッドワード)を移動し、REP
プレフィックスは指定された回数だけこの操作を繰り返します。非常に大きなデータブロックのコピーには適していますが、命令の実行開始に一定のオーバーヘッド(スタートアップコスト)があります。- SSEレジスタを用いた
MOV
命令: SSE(Streaming SIMD Extensions)は、x86プロセッサの命令セット拡張であり、SIMD(Single Instruction, Multiple Data)操作を可能にします。これには、128ビットのXMMレジスタを使用するMOVOU
(Move Unaligned Oword)のような命令が含まれ、一度に16バイトのデータを移動できます。
このコミット以前のmemmove
の実装では、256バイトを超えるメモリ領域のコピーにはREP MOVSQ
が使用されていました。しかし、ベンチマークの結果、約2KB(2048バイト)未満のサイズでは、REP MOVSQ
のスタートアップコストが原因で、SSEレジスタを用いたMOV
命令の方が高速であることが判明しました。
この変更の背景には、特に257バイトから2048バイトという中間サイズのメモリコピーにおいて、REP MOVSQ
の非効率性を解消し、より高速なMOV
命令に切り替えることで、Goプログラム全体のパフォーマンスを向上させるという目的があります。コミットメッセージに記載されているベンチマーク結果は、この最適化が特に512バイト、1024バイト、2048バイトのコピーで顕著な速度向上をもたらしたことを明確に示しています。
前提知識の解説
memmove
: C言語の標準ライブラリ関数であり、Goランタイムでも内部的に使用されるメモリコピー関数です。memcpy
と異なり、コピー元とコピー先のメモリ領域が重なっていても正しく動作することが保証されています。これは、重なりがある場合にコピー方向を調整することで実現されます。amd64
アーキテクチャ: IntelおよびAMDが開発した64ビットのx86命令セットアーキテクチャです。Go言語は、このアーキテクチャ上で非常に効率的に動作するように設計されており、パフォーマンスが重要なランタイム部分はアセンブリ言語で記述されることがあります。REP MOVSQ
命令: x86アセンブリ命令の一つで、MOVSQ
(Move String Quadword)命令をREP
(Repeat)プレフィックスと組み合わせて使用します。MOVSQ
はRSI
(ソースインデックスレジスタ)が指すアドレスからRDI
(デスティネーションインデックスレジスタ)が指すアドレスへ8バイトのデータをコピーし、両方のレジスタを更新します。REP
プレフィックスは、RCX
(カウントレジスタ)がゼロになるまでこの操作を繰り返します。これは、大量の連続したメモリブロックをコピーする際に非常に効率的ですが、命令のセットアップと終了に一定の時間がかかります。- SSE (Streaming SIMD Extensions): Intelが開発したSIMD(Single Instruction, Multiple Data)命令セットの拡張です。SSEは、浮動小数点演算やメディア処理の高速化を目的として導入されましたが、メモリ操作にも利用されます。SSEは128ビットのXMMレジスタ(XMM0からXMM15)を使用し、一度に16バイトのデータを処理できます。
MOVOU
命令: SSE命令の一つで、「Move Unaligned Oword」の略です。これは、アラインメントされていないメモリ位置から16バイト(Oword = Octa-word = 128ビット)のデータをXMMレジスタにロードしたり、XMMレジスタからメモリにストアしたりするために使用されます。アラインメントを気にせずに高速なメモリコピーを行うのに適しています。
- Goランタイムにおけるアセンブリ言語: Go言語のランタイム(ガベージコレクタ、スケジューラ、プリミティブなメモリ操作など)のパフォーマンスが非常に重要な部分は、Goアセンブリ言語(Plan 9アセンブラの構文に基づく)で記述されています。これにより、特定のハードウェア機能(例: SSE命令)を最大限に活用し、最高のパフォーマンスを引き出すことが可能になります。
技術的詳細
このコミットの主要な技術的変更は、memmove
関数がメモリコピーのサイズに応じて異なる最適化パスを選択するロジックを調整し、新たに中間サイズのコピーに特化したSSEベースのルーチンを導入した点にあります。
以前の実装では、メモリコピーのサイズが256バイトを超えると、効率的なREP MOVSQ
命令に切り替えていました。しかし、REP MOVSQ
は命令の実行開始にオーバーヘッドがあるため、257バイトから約2048バイト(2KB)の範囲では、このオーバーヘッドがコピーの利点を上回ることがありました。
このコミットでは、以下の変更が行われました。
- カットオーバーサイズの変更:
REP MOVSQ
に切り替える閾値が、従来の「約1K(1024バイト)」から「約2K(2048バイト)」に変更されました。これは、2048バイト未満のコピーに対して、より高速なSSEベースのMOV
命令を使用するための準備です。 - 新しいコピーパスの導入:
move_256through2048
という新しいアセンブリルーチンが導入されました。このルーチンは、256バイトを超えるが2048バイト以下の非重複メモリコピーを処理するために設計されています。 - SSE
MOVOU
命令の活用:move_256through2048
ルーチン内では、一連のMOVOU
命令が使用されます。具体的には、16個のXMMレジスタ(X0からX15)を使い、それぞれ16バイトずつ、合計256バイトのデータをソースからレジスタに一括でロードし、その後、これらのレジスタの内容をデスティネーションに一括でストアします。これにより、256バイトのブロックを非常に高速にコピーできます。 - ループ処理: 256バイトを超えるが2048バイト以下のコピーの場合、
move_256through2048
ルーチンは256バイトのブロックをコピーした後、残りのバイト数に応じてループし、次の256バイトブロックを処理します。これにより、中間サイズのコピー全体を効率的に処理します。 - 重複領域の考慮: コミットメッセージのコメントにもあるように、
move_256through2048
ルーチンは、ソースとデスティネーションのメモリ領域が重複しない場合、またはコピー方向が順方向(アドレスが増加する方向)の場合にのみ使用できます。これは、データをレジスタに完全にロードしてから書き出す小さなコピー(例:move_129through256
)とは異なり、部分的にデータを処理するため、重複領域の扱いに注意が必要です。
この最適化により、特に512バイト、1024バイト、2048バイトといったサイズのメモリコピーにおいて、ナノ秒単位で大幅な時間短縮が実現され、スループット(MB/s)も向上しています。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/memmove_amd64.s
ファイルに集中しています。
tail
ラベル付近のロジック変更:REP
命令のスタートアップコストに関するコメントが更新され、カットオーバーサイズが「約1K」から「約2K」に変更されました。tail
ラベルの直後に、move_129through256
やそれ以下のサイズが重複領域でも動作するのに対し、move_256through2048
は非重複または順方向コピーでのみ使用可能であるという重要なコメントが追加されました。
forward
ラベル付近のロジック変更:forward
パス(順方向コピー)において、コピーサイズBX
が$2048
以下の場合に、新しく追加されたmove_256through2048
ルーチンにジャンプする条件分岐(CMPQ BX, $2048
とJLS move_256through2048
)が追加されました。これにより、中間サイズのコピーが新しい最適化パスにルーティングされます。
move_256through2048
ルーチンの追加:- ファイルの下部に、
move_256through2048
という新しいアセンブリルーチンが追加されました。 - このルーチンは、まずコピーサイズ
BX
から256を引きます(SUBQ $256, BX
)。 - 次に、
MOVOU
命令を16回連続して使用し、ソースアドレスSI
から16バイトずつXMMレジスタ(X0からX15)にロードします。これにより、合計256バイトがレジスタに読み込まれます。 - その後、同様に
MOVOU
命令を16回連続して使用し、XMMレジスタの内容をデスティネーションアドレスDI
に16バイトずつストアします。 - コピーサイズ
BX
がまだ256バイト以上の場合(CMPQ BX, $256
とJGE move_256through2048
)、SI
とDI
を256バイト進めて(LEAQ 256(SI), SI
、LEAQ 256(DI), DI
)、ルーチン自身にジャンプして次の256バイトブロックを処理します。 - 残りのサイズが256バイト未満になった場合、
JMP tail
で既存の小さなコピー処理ルーチンに制御を移します。
- ファイルの下部に、
コアとなるコードの解説
新しく追加されたmove_256through2048
ルーチンは、Goランタイムのmemmove
における中間サイズのメモリコピーのボトルネックを解消するために設計されています。
このルーチンは、256バイトのブロックを効率的にコピーするために、SSE命令のMOVOU
を最大限に活用しています。
- 一括ロード:
MOVOU (SI), X0
からMOVOU 240(SI), X15
までの一連の命令は、ソースポインタSI
から始まる256バイトのデータを、16個のXMMレジスタ(X0-X15)に一括でロードします。各MOVOU
命令は16バイトをロードするため、16 * 16 = 256バイトが一度にレジスタセットに読み込まれます。 - 一括ストア: ロードが完了した後、
MOVOU X0, (DI)
からMOVOU X15, 240(DI)
までの一連の命令は、XMMレジスタにロードされた256バイトのデータを、デスティネーションポインタDI
に一括でストアします。 - ループ処理:
SUBQ $256, BX
: コピーすべき残りバイト数BX
から、今回処理した256バイトを引きます。LEAQ 256(SI), SI
とLEAQ 256(DI), DI
: ソースとデスティネーションのポインタをそれぞれ256バイト進めます。CMPQ BX, $256
とJGE move_256through2048
: もし残りバイト数BX
がまだ256バイト以上であれば、move_256through2048
ルーチンの先頭にジャンプし、次の256バイトブロックのコピーを繰り返します。
- 残りの処理: ループが終了し、残りバイト数
BX
が256バイト未満になった場合、JMP tail
命令によって、既存のmemmove
のロジック(tail
ラベル)に制御が戻されます。tail
ラベル以下のコードは、残りの小さなバイト数を処理するための効率的なルーチン(例: 128バイト、64バイト、32バイト、16バイト、8バイト、1バイト単位のコピー)に分岐します。
この構造により、257バイトから2048バイトの範囲のメモリコピーは、REP MOVSQ
のオーバーヘッドを回避し、SSE命令による高速なブロックコピーと、残りのバイトに対する既存の最適化された処理を組み合わせることで、全体として大幅なパフォーマンス向上を実現しています。
関連リンク
- Go CL 90500043: https://golang.org/cl/90500043
参考にした情報源リンク
- Go runtime memmove amd64 SSE REP MOVSQ optimizationに関するWeb検索結果:
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFYAas8ttuOca8Udeo1Yt5QoZ-H1PRUR0j0xsvb_9xWiOSd3iWaiYpnB1urIj_kx534YlqIfsQGQj-WTmmz7KPhwCpfUOxwpP_K5el6s5EwVxNBTkNXMACIBO3C-CSeRfDiVrpUmHXuYtBem0E97g==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH6DQ7K1_I9EiO3JVcDkt3XPtf7sy6iYROIc3ekR_jovF_P0BUEtQ1MJBXxBQsHVQRRvyAE8j6RUWDdeJxbXz4Gq7TaeLHK1RVlfv1feJ77fC7J6uDPmB0BG3DYUHcRymyo4Rk8lQ==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGBUZpSHwyFY-GNp_3-7R471CsYKJ6E_trxXytrulLUoHLHBAQS_xfFEdVNpChRqtA8CkchEglx2_qokjxCU1MfJxJK_NVEP-QYkUZWGoabku4wUI6vUy9343FwVYhSFxwhJWPZ77mQS5JLkv0szvtXI7Er5cIovJEA
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFGLapT_b9C4IJ3bKYbdNRX2efOfYssgTp9xmqVSlo7EanJCAvgekg2l4nG41dA7eHugovqK5677G7rwQf_Rp0MxmrfOTuVqaWkL80Ly3wqu-r9RtLMlnn43VyYAeBYIXaODfeu
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHco-DLIsWvU2NBjROVrMuKymfJEqT5WQQVdJYQH61nDNTSJhwZ_qrKBrU-ULBNQwGvMFUo4ZN8IZ9x-DSrNvmLolFkYOsjHsrvihtZS7ei2Eosvhg5kXuxJVRF1ZcaZHuhW0_5