との主な違いはmemcpy()
、送信元と宛先が重複している場合に正常に機能するmemmove()
ことです。memmove()
バッファが確実にオーバーラップしない場合は、 memcpy()の方が高速である可能性があるため、望ましいです。
私を悩ませているのは、これが潜在的にです。それはマイクロ最適化ですか、それとも、どこにでも固執するのではなく、memcpy()
実際に使用する必要があるように、より高速である場合の実際の重要な例がありますか?memcpy()
memmove()
memmove()
コンパイラがオーバーラップが不可能であると推測できない場合に備えて、少なくとも前方または後方にコピーする暗黙の分岐があります。これは、を優先して最適化する機能がない場合、memcpy()
少なくともmemmove()
1つのブランチによって遅くなり、各ケースを処理するためのインライン化された命令によって占有される追加のスペースがあることを意味します(インライン化が可能な場合)。
eglibc-2.11.1
両方のコードを読み取り、これが疑わしいことmemcpy()
を確認します。memmove()
さらに、後方コピー中にページがコピーされる可能性はありません。大幅な高速化は、重複する可能性がない場合にのみ利用できます。
要約すると、これは次のことを意味します。リージョンがオーバーラップしないことを保証できる場合は、memcpy()
オーバーを選択するmemmove()
と分岐が回避されます。ソースと宛先に対応するページ整列およびページサイズの領域が含まれ、重複しない場合、一部のアーキテクチャでは、呼び出したかに関係なく、これらの領域にハードウェアアクセラレーションコピーを採用できmemmove()
ますmemcpy()
。
私が上にリストした仮定と観察を超えて、実際にはもう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);
s1
2つのポインタを想定し、s2
重複するメモリを指さないため、の単純なC実装でmemcpy
は、これを利用して、アセンブラに頼ることなく、より効率的なコードを生成できます。詳細については、こちらを参照してください。これは可能だと確信していmemmove
ますが、に存在するものよりも追加のチェックが必要になりますeglibc
。つまり、これらの関数のC実装の場合、パフォーマンスコストは単一のブランチよりもわずかに高くなる可能性があります。
せいぜい、ポインタ比較と条件分岐を保存するのmemcpy
ではなく、呼び出すことです。memmove
大きなコピーの場合、これはまったく重要ではありません。小さなコピーをたくさん行う場合は、違いを測定する価値があるかもしれません。それが重要かどうかを判断する唯一の方法です。
memcpy
それは間違いなくマイクロ最適化ですが、それが安全であることを簡単に証明できるときに使用すべきではないという意味ではありません。時期尚早の悲観化は多くの悪の根源です。
さて、ソースと宛先がオーバーラップしmemmove
、ソースが宛先の前にある場合は、逆方向にコピーする必要があります。したがって、2つの領域がオーバーラップしているかどうかに関係なく、ソースが宛先の前にある場合、単純に逆方向にコピーする実装もあります。memmove
の高品質な実装でmemmove
は、領域がオーバーラップしているかどうかを検出し、オーバーラップしていない場合はフォワードコピーを実行できます。このような場合、比較される唯一の余分なオーバーヘッドmemcpy
は、単にオーバーラップチェックです。
簡単にmemmove
言うと、重複をテストしてから適切なことを行う必要があります。を使用するとmemcpy
、重複がないため、追加のテストは不要であると主張します。
memcpy
そうは言っても、ととまったく同じコードを持つプラットフォームを見てきましたmemmove
。
memcpy
を呼び出すだけである可能性は確かにあります。memmove
その場合、を使用してもメリットはありませんmemcpy
。反対に、実装者が想定memmove
することはめったに使用されず、Cで可能な限り単純な一度に1バイトのループで実装される可能性があります。その場合、最適化されたものより10倍遅くなる可能性がありますmemcpy
。他の人が言っているように、最も可能性の高いケースは、転送コピーが可能であることを検出したときにmemmove
使用memcpy
することですが、一部の実装では、重複を探すことなく送信元アドレスと宛先アドレスを単純に比較する場合があります。
そうは言ってもmemmove
、単一のバッファ内でデータをシフトする場合を除いて、絶対に使用しないことをお勧めします。遅くはないかもしれませんが、それでもそうかもしれません。それでは、必要がないとわかっているのに、なぜそれを危険にさらすのmemmove
でしょうか。
単純化して常に使用してmemmove
ください。常に正しい関数は、半分の時間しか正しくない関数よりも優れています。
ほとんどの実装では、memmove()関数呼び出しのコストが、両方の動作が定義されているシナリオでmemcpy()よりも大幅に高くならない可能性があります。ただし、まだ言及されていない2つのポイントがあります。
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を呼び出すことができます。安全な。しかし、そのような最適化を行うものはありません。