memmove(3)で時間の50%を費やすアプリケーションのパフォーマンスホットスポットを調査しています。アプリケーションは、何百万もの4バイト整数をソートされた配列に挿入し、memmoveを使用してデータを「右に」シフトし、挿入された値のためのスペースを作ります。
メモリのコピーは非常に高速であると私は予想しており、memoveに多くの時間が費やされていることに驚きました。しかし、それからmemmoveは、メモリの大きなページをコピーするのではなく、タイトなループで実装する必要がある重複領域を移動するため、遅いと考えました。memcpyとmemmoveの間にパフォーマンスの違いがあるかどうかを確認するために、小さなマイクロベンチマークを書きました。memcpyが勝つことを期待しています。
私は2つのマシン(コアi5、コアi7)でベンチマークを実行し、memmpyがmemcpyよりも実際に高速であることを確認しました。古いコアi7では、2倍近くも高速です!今、私は説明を探しています。
これが私のベンチマークです。memcpyで100 mbをコピーし、memmoveで約100 mb移動します。ソースと宛先が重複しています。発信元と宛先のさまざまな「距離」が試されます。各テストは10回実行され、平均時間が印刷されます。
https://gist.github.com/cruppstahl/78a57cdf937bca3d062c
Core i5(Linux 3.5.0-54-generic#81〜precise1-Ubuntu SMP x86_64 GNU / Linux、gccは4.6.3(Ubuntu / Linaro 4.6.3-1ubuntu5)での結果です。角かっこ内の数値はソースと宛先の間の距離(ギャップサイズ):
memcpy 0.0140074
memmove (002) 0.0106168
memmove (004) 0.01065
memmove (008) 0.0107917
memmove (016) 0.0107319
memmove (032) 0.0106724
memmove (064) 0.0106821
memmove (128) 0.0110633
MemmoveはSSEに最適化されたアセンブラーコードとして実装され、後ろから前へコピーします。ハードウェアプリフェッチを使用してデータをキャッシュにロードし、128バイトをXMMレジスタにコピーして、宛先に格納します。
(memcpy-ssse3-back.S、1650行目以降)
L(gobble_ll_loop):
prefetchnta -0x1c0(%rsi)
prefetchnta -0x280(%rsi)
prefetchnta -0x1c0(%rdi)
prefetchnta -0x280(%rdi)
sub $0x80, %rdx
movdqu -0x10(%rsi), %xmm1
movdqu -0x20(%rsi), %xmm2
movdqu -0x30(%rsi), %xmm3
movdqu -0x40(%rsi), %xmm4
movdqu -0x50(%rsi), %xmm5
movdqu -0x60(%rsi), %xmm6
movdqu -0x70(%rsi), %xmm7
movdqu -0x80(%rsi), %xmm8
movdqa %xmm1, -0x10(%rdi)
movdqa %xmm2, -0x20(%rdi)
movdqa %xmm3, -0x30(%rdi)
movdqa %xmm4, -0x40(%rdi)
movdqa %xmm5, -0x50(%rdi)
movdqa %xmm6, -0x60(%rdi)
movdqa %xmm7, -0x70(%rdi)
movdqa %xmm8, -0x80(%rdi)
lea -0x80(%rsi), %rsi
lea -0x80(%rdi), %rdi
jae L(gobble_ll_loop)
memmpyがmemcpyより速いのはなぜですか?memcpyがメモリページをコピーすることを期待します。これはループよりもはるかに高速です。最悪の場合、memcpyはmemmoveと同じくらい高速になると予想します。
PS:私のコードではmemmoveをmemcpyに置き換えることができないことを知っています。コードサンプルにはCとC ++が混在しています。この質問は、実際には学術目的のためだけのものです。
アップデート1
さまざまな答えに基づいて、テストのバリエーションをいくつか実行しました。
- memcpyを2回実行すると、2回目の実行は最初の実行よりも高速になります。
- memcpy(
memset(b2, 0, BUFFERSIZE...)
)の宛先バッファに「触れる」と、memcpy の最初の実行も高速になります。 - memcpyはmemmoveよりも少し遅いです。
結果は次のとおりです。
memcpy 0.0118526
memcpy 0.0119105
memmove (002) 0.0108151
memmove (004) 0.0107122
memmove (008) 0.0107262
memmove (016) 0.0108555
memmove (032) 0.0107171
memmove (064) 0.0106437
memmove (128) 0.0106648
私の結論:@Oliver Charlesworthからのコメントに基づいて、memcpy宛先バッファーが初めてアクセスされるとすぐに、オペレーティングシステムは物理メモリをコミットする必要があります(誰かがこれを「証明」する方法を知っている場合は、答えを追加してください! )。さらに、@ Mats Peterssonが言ったように、memmoveはmemcpyよりもキャッシュフレンドリです。
すばらしい回答とコメントをありがとう!