web-dev-qa-db-ja.com

memmoveがmemcpyより速いのはなぜですか?

Memmove(3)で時間の50%を費やしているアプリケーションのパフォーマンスホットスポットを調査しています。アプリケーションは、数百万の4バイト整数をソートされた配列に挿入し、memmoveを使用してデータを「右」にシフトし、挿入された値のスペースを確保します。

私の期待は、メモリのコピーが非常に高速であり、memmoveに多くの時間が費やされていることに驚きました。しかし、memmoveは、メモリの大きなページをコピーするのではなく、タイトループで実装する必要がある重複領域を移動するため、遅いという考えがありました。 memcpyとmemmoveのパフォーマンスに違いがあるかどうかを調べるために、memcpyが勝つことを期待して、小さなマイクロベンチマークを作成しました。

2台のマシン(コアi5、コアi7)でベンチマークを実行しましたが、memmoveは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 ff)

_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よりmemmoveが速いのはなぜですか? memcpyはメモリページをコピーすることを期待します。これはループよりもはるかに高速です。最悪の場合、memcpyはmemmoveと同じくらい高速になると予想されます。

PS:自分のコードでmemmoveをmemcpyに置き換えることができないことを知っています。コードサンプルにはCとC++が混在していることがわかります。この質問は、まさに学術目的のためです。

更新1

さまざまな答えに基づいて、テストのいくつかのバリエーションを実行しました。

  1. Memcpyを2回実行すると、2回目の実行は最初の実行よりも高速になります。
  2. Memcpy(memset(b2, 0, BUFFERSIZE...))の宛先バッファーを「タッチ」すると、memcpyの最初の実行も高速になります。
  3. 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よりもキャッシュに優しいです。

すべての素晴らしい回答とコメントをありがとう!

87
cruppstahl

memmove呼び出しは2〜128バイトずつメモリをシャッフルしますが、memcpy呼び出し元と宛先は完全に異なります。どういうわけかそれがパフォーマンスの違いを説明しています。同じ場所にコピーすると、memcpyが最終的に少し速くなることがわかります。 on ideone.com

memmove (002) 0.0610362
memmove (004) 0.0554264
memmove (008) 0.0575859
memmove (016) 0.057326
memmove (032) 0.0583542
memmove (064) 0.0561934
memmove (128) 0.0549391
memcpy 0.0537919

しかし、ほとんど何もありません-すでに障害が発生しているメモリページに書き戻すことがmuchの影響を与えるという証拠はなく、確かに時間が...しかし、それは、リンゴ対リンゴと比較した場合、memcpyを不必要に遅くすることに何の問題もないことを示しています。

55
Tony Delroy

memcpyを使用している場合、書き込みはキャッシュに入れる必要があります。 memmoveを使用する場合、小さなステップをコピーするときに、コピーするメモリはすでにキャッシュにあります(2バイト、4バイト、16バイト、または128バイトが「戻る」ため)。宛先が数メガバイト(> 4 *キャッシュサイズ)のmemmoveを実行してみてください。同様の結果が得られると思います(ただし、テストすることはできません)。

大規模なメモリ操作を行うときは、すべてがキャッシュのメンテナンスに関することを保証します。

22
Mats Petersson

歴史的に、memmoveとmemcopyは同じ機能です。彼らは同じように働き、同じ実装を持っていました。その後、重複する領域を特定の方法で処理するために、memcopyを定義する必要がない(そして頻繁に定義されなかった)ことがわかりました。

最終的な結果として、memmoveは、パフォーマンスに影響があったとしても、重複する領域を特定の方法で処理するように定義されています。 Memcopyは、重複しない領域で利用可能な最適なアルゴリズムを使用することになっています。通常、実装はほとんど同じです。

遭遇した問題は、x86ハードウェアには非常に多くのバリエーションがあるため、メモリをシフトする方法が最も速いかどうかを判断できないことです。また、ある状況で結果が得られたと思ったとしても、メモリレイアウトに異なる「ストライド」があるという単純なことが、キャッシュパフォーマンスを大きく変える可能性があります。

実際に実行していることをベンチマークするか、問題を無視して、Cライブラリに対して行われたベンチマークに依存することができます。

編集:ああ、そして最後にもう一つ。多くのメモリ内容を移動するのは非常に遅いです。整数を処理するための単純なBツリー実装のようなものを使用すると、アプリケーションの実行が高速になると思います。 (ああ、あなたは大丈夫)

編集2:コメントで私の拡大を要約すると:マイクロベンチマークはここでの問題であり、あなたがそれが何であるかを測定するものではありません。 memcpyとmemmoveに与えられるタスクは、互いに大きく異なります。 memmoveまたはmemcpyでmemcpyに与えられたタスクが複数回繰り返される場合、最終結果は、領域がオーバーラップしない限り、使用するメモリシフト関数に依存しません。

15
user3710044

「memcpyはmemmoveよりも効率的です。」あなたの場合、2つの関数を実行している間はおそらくまったく同じことをしていないでしょう。

一般に、必要な場合にのみmemmoveを使用してください。コピー元とコピー先の領域が重複している可能性が非常に高い場合に使用します。

参照: https://www.youtube.com/watch?v=Yr1YnOVG-4g Dr. Jerry Cain、(Stanford Intro Systems Lecture-7)時間:36:00

2
Ehsan