16

との主な違いmemcpy()、送信元と宛先が重複している場合に正常に機能するmemmove()ことです。memmove()バッファが確実にオーバーラップしない場合は、 memcpy()の方が高速である可能性があるため、望ましいです。

私を悩ませているのは、これが潜在的にです。それはマイクロ最適化ですか、それとも、どこにでも固執するのではなく、memcpy()実際に使用する必要があるように、より高速である場合の実際の重要な例がありますか?memcpy()memmove()

4

7 に答える 7

20

memmove()コンパイラがオーバーラップが不可能であると推測できない場合に備えて、少なくとも前方または後方にコピーする暗黙の分岐があります。これは、を優先して最適化する機能がない場合、memcpy()少なくともmemmove()1つのブランチによって遅くなり、各ケースを処理するためのインライン化された命令によって占有される追加のスペースがあることを意味します(インライン化が可能な場合)。

eglibc-2.11.1両方のコードを読み取り、これが疑わしいことmemcpy()を確認します。memmove()さらに、後方コピー中にページがコピーされる可能性はありません。大幅な高速化は、重複する可能性がない場合にのみ利用できます。

要約すると、これは次のことを意味します。リージョンがオーバーラップしないことを保証できる場合は、memcpy()オーバーを選択するmemmove()と分岐が回避されます。ソースと宛先に対応するページ整列およびページサイズの領域が含まれ、重複しない場合、一部のアーキテクチャでは、呼び出したかに関係なく、これらの領域にハードウェアアクセラレーションコピーを採用できmemmove()ますmemcpy()

Update0

私が上にリストした仮定と観察を超えて、実際にはもう1つの違いがあります。C99の時点で、2つの関数に対して次のプロトタイプが存在します。

void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
void *memmove(void * s1, const void * s2, size_t n);

s12つのポインタを想定し、s2重複するメモリを指さないため、の単純なC実装でmemcpyは、これを利用して、アセンブラに頼ることなく、より効率的なコードを生成できます。詳細については、こちらを参照してください。これは可能だと確信していmemmoveますが、に存在するものよりも追加のチェックが必要になりますeglibc。つまり、これらの関数のC実装の場合、パフォーマンスコストは単一のブランチよりもわずかに高くなる可能性があります。

于 2010-09-13T14:10:56.517 に答える
13

せいぜい、ポインタ比較と条件分岐を保存するのmemcpyではなく、呼び出すことです。memmove大きなコピーの場合、これはまったく重要ではありません。小さなコピーをたくさん行う場合は、違いを測定する価値があるかもしれません。それが重要かどうかを判断する唯一の方法です。

memcpyそれは間違いなくマイクロ最適化ですが、それが安全であることを簡単に証明できるときに使用すべきではないという意味ではありません。時期尚早の悲観化は多くの悪の根源です。

于 2010-09-13T14:07:08.443 に答える
4

さて、ソースと宛先がオーバーラップmemmove、ソースが宛先の前にある場合は、逆方向にコピーする必要があります。したがって、2つの領域がオーバーラップしているかどうかに関係なく、ソースが宛先の前にある場合、単純に逆方向にコピーする実装もあります。memmove

の高品質な実装でmemmoveは、領域がオーバーラップしているかどうかを検出し、オーバーラップしていない場合はフォワードコピーを実行できます。このような場合、比較される唯一の余分なオーバーヘッドmemcpyは、単にオーバーラップチェックです。

于 2010-09-13T13:51:11.657 に答える
2

簡単にmemmove言うと、重複をテストしてから適切なことを行う必要があります。を使用するとmemcpy、重複がないため、追加のテストは不要であると主張します。

memcpyそうは言っても、ととまったく同じコードを持つプラットフォームを見てきましたmemmove

于 2010-09-13T13:52:57.503 に答える
2

memcpyを呼び出すだけである可能性は確かにあります。memmoveその場合、を使用してもメリットはありませんmemcpy。反対に、実装者が想定memmoveすることはめったに使用されず、Cで可能な限り単純な一度に1バイトのループで実装される可能性があります。その場合、最適化されたものより10倍遅くなる可能性がありますmemcpy。他の人が言っているように、最も可能性の高いケースは、転送コピーが可能であることを検出したときにmemmove使用memcpyすることですが、一部の実装では、重複を探すことなく送信元アドレスと宛先アドレスを単純に比較する場合があります。

そうは言ってもmemmove、単一のバッファ内でデータをシフトする場合を除いて、絶対に使用しないことをお勧めします。遅くはないかもしれませんが、それでもそうかもしれません。それでは、必要がないとわかっているのに、なぜそれを危険にさらすのmemmoveでしょうか。

于 2010-09-13T18:01:32.313 に答える
2

単純化して常に使用してmemmoveください。常に正しい関数は、半分の時間しか正しくない関数よりも優れています。

于 2010-09-13T18:12:08.793 に答える
2

ほとんどの実装では、memmove()関数呼び出しのコストが、両方の動作が定義されているシナリオでmemcpy()よりも大幅に高くならない可能性があります。ただし、まだ言及されていない2つのポイントがあります。

  1. 一部の実装では、アドレスの重複の決定にコストがかかる場合があります。標準Cには、ソースオブジェクトと宛先オブジェクトが同じ割り当てられたメモリ領域を指しているかどうかを判断する方法はありません。したがって、猫と犬に自発的に大なり記号または小なり記号を使用させることなく、それらに大なり記号または小なり記号を使用することはできません。互いに仲良くする(または他の未定義動作を呼び出す)。実際の実装には、ポインタが重複しているかどうかを判断するための効率的な手段がある可能性がありますが、標準ではそのような手段が存在する必要はありません。完全にポータブルCで記述されたmemmove()関数は、多くのプラットフォームで、完全にポータブルCで記述されたmemcpy()の実行に少なくとも2倍の時間がかかる可能性があります。
  2. 実装は、セマンティクスを変更しない場合に、関数をインラインで拡張できます。80x86コンパイラでは、ESIおよびEDIレジスタが重要なものを保持していない場合、memcpy(src、dest、1234)は次のコードを生成する可能性があります。
      mov esi、[src]
      mov edi、[dest]
      mov ecx、1234/4; コンパイラはそれが定数であることに気付く可能性があります
      cld
      担当者movsl
    
    これには同じ量のインラインコードが必要ですが、実行速度は次のとおりです。
      プッシュ[src]
      [宛先]を押す
      プッシュdword1234
      _memcpyを呼び出す
    
      ..。
    
    _memcpy:
      ebpをプッシュ
      mov ebp、esp
      mov ecx、[ebp + numbytes]
      テストecx、3; 4の倍数かどうかを確認します
      jzmultiple_of_four
    
    multiple_of_four:
      プッシュesi; 発信者がこの値を保持する必要があるかどうかわからない
      ediを押す; 発信者がこの値を保持する必要があるかどうかわからない
      mov esi、[ebp + src]
      mov edi、[ebp + dest]
      担当者movsl
      ポップエディ
      ポップesi
      ret  
    

かなりの数のコンパイラがmemcpy()を使用してこのような最適化を実行します。memmoveでそれを行うものはありませんが、最適化されたバージョンのmemcpyがmemmoveと同じセマンティクスを提供する場合があります。たとえば、numbytesが20の場合:

; eax、ebx、ecx、edx、esi、およびediの値を想定する必要はありません
  mov esi、[src]
  mov eax、[esi]
  mov ebx、[esi + 4]
  mov ecx、[esi + 8]
  mov edx、[esi + 12]
  mov edi、[esi + 16]
  mov esi、[dest]
  mov [esi]、eax
  mov [esi + 4]、ebx
  mov [esi + 8]、ecx
  mov [esi + 12]、edx
  mov [esi + 16]、edi

これは、アドレス範囲が重複している場合でも正しく機能します。これは、領域全体のコピーを(レジスタ内で)効果的に作成してから、いずれかの領域が書き込まれる前に移動するためです。理論的には、コンパイラはmemmove()を処理して、memcpy()として処理すると、アドレス範囲が重複している場合でも安全な実装が生成されるかどうかを確認し、memcpy()実装を置き換えることができない場合は_memmoveを呼び出すことができます。安全な。しかし、そのような最適化を行うものはありません。

于 2011-11-20T22:16:30.617 に答える