C++で1つの配列から別の配列に要素をコピーしています。rep movs
ESIの配列をサイズECXのEDIの配列にコピーするように見えるx86の命令を見つけました。ただし、VS 2008(Intel Xeon x64プロセッサ上)の命令にコンパイルしようとしたfor
norループはどちらもありません。この命令にコンパイルされるコードをどのように書くことができますか?while
rep movs
6 に答える
正直なところ、あなたはすべきではありません。REP は、命令セットの古いホールドオーバーのようなものであり、CPU 内でマイクロコード化されたサブルーチンを呼び出す必要があるため、実際にはかなり遅く、ROM ルックアップ レイテンシがあり、パイプライン化されていません。
ほぼすべての実装で、memcpy()
コンパイラ組み込み関数の方が使いやすく、実行速度も速いことがわかります。
MSVC には、プレフィックス付き命令を生成する__movsxxx
&__stosxxx
組み込み関数があります。REP
crt での sse2 分岐により、組み込みが存在しなくなったため、vc9+ の下で組み込みのmemset
別名を強制する「ハック」もあります。REP STOS
これは__stosxxx
、コンパイラが定数用に最適化し、正しく並べ替えることができるため、より優れています。
#define memset(mem,fill,size) memset((DWORD*)mem,((fill) << 24|(fill) << 16|(fill) << 8|(fill)),size)
__forceinline void memset(DWORD* pStart, unsigned long dwFill, size_t nSize)
{
//credits to Nepharius for finding this
DWORD* pLast = pStart + (nSize >> 2);
while(pStart < pLast)
*pStart++ = dwFill;
if((nSize &= 3) == 0)
return;
if(nSize == 3)
{
(((WORD*)pStart))[0] = WORD(dwFill);
(((BYTE*)pStart))[2] = BYTE(dwFill);
}
else if(nSize == 2)
(((WORD*)pStart))[0] = WORD(dwFill);
else
(((BYTE*)pStart))[0] = BYTE(dwFill);
}
もちろんREP
、常に使用するのが最適であるとは限りません。「ホット」な領域用のカスタム アセンブリを作成する気がしない限り、memcpy
sse2 またはシステムに基づいて (msvc の下で) 分岐します。 REPS MOV
.
まさにその命令が必要な場合は、組み込みのアセンブラを使用して、その命令を手動で記述してください。特定のマシンコードを生成するためにコンパイラに依存することはできません.1回のコンパイルで生成されたとしても、次のコンパイル時に他の同等のものを生成することを決定できます。
歴史的なメモ(メーカーの戦略についての洞察がない)では、「rep movs *」(など)の指示が非常に遅い時期がありました。Pentium /PentiumMMXの頃だったと思います。私の同僚(私よりも洞察力があった)は、メーカーが担当者の処理に割り当てられるチップ領域を減らし(<=>トランジスタが少なく/マイクロコードが多い)、それを使用して他のより使用される命令を高速化したと述べました。
担当者が再び比較的速く話せるようになってから15年ほどで、より多くのトランジスタ/より少ないマイクロコードが示唆されます。
cmps*、movs*、scas*、stos* 命令バリアントと一緒に rep* プレフィックス バリアントを使用して、コード サイズを最小限に抑え、不要な呼び出し/ジャンプを回避し、キャッシュによって実行される作業を抑えるインライン コードを生成します。別の方法は、パラメーターを設定して memset または memcpy を別の場所で呼び出すことです。これは、100 バイト以上をコピーする場合は全体的に高速になる可能性がありますが、10 ~ 20 バイトの問題である場合は、rep を使用する方が高速です (または少なくとも最後に測定したとき)。
私のコンパイラは、インライン アセンブリ関数の仕様と使用を許可し、最適化アクティビティにそれらのレジスタの使用/変更を含めるため、状況が適切な場合にそれらを使用することが可能です。