メモリコピールーチンは、次のようなポインタを介した単純なメモリコピーよりもはるかに複雑で高速です。
void simple_memory_copy(void* dst, void* src, unsigned int bytes)
{
unsigned char* b_dst = (unsigned char*)dst;
unsigned char* b_src = (unsigned char*)src;
for (int i = 0; i < bytes; ++i)
*b_dst++ = *b_src++;
}
改善点
最初にできる改善は、ポインターの1つをワード境界に揃えることです(つまり、ネイティブの整数サイズ、通常32ビット/ 4バイトですが、新しいアーキテクチャでは64ビット/ 8バイトにすることができます)。ワードサイズの移動を使用します。 /コピー手順。これには、ポインタが揃うまでバイトからバイトへのコピーを使用する必要があります。
void aligned_memory_copy(void* dst, void* src, unsigned int bytes)
{
unsigned char* b_dst = (unsigned char*)dst;
unsigned char* b_src = (unsigned char*)src;
// Copy bytes to align source pointer
while ((b_src & 0x3) != 0)
{
*b_dst++ = *b_src++;
bytes--;
}
unsigned int* w_dst = (unsigned int*)b_dst;
unsigned int* w_src = (unsigned int*)b_src;
while (bytes >= 4)
{
*w_dst++ = *w_src++;
bytes -= 4;
}
// Copy trailing bytes
if (bytes > 0)
{
b_dst = (unsigned char*)w_dst;
b_src = (unsigned char*)w_src;
while (bytes > 0)
{
*b_dst++ = *b_src++;
bytes--;
}
}
}
アーキテクチャが異なると、ソースポインターまたは宛先ポインターが適切に配置されているかどうかに基づいて、動作が異なります。たとえば、XScaleプロセッサでは、ソースポインターではなく宛先ポインターを調整することで、パフォーマンスが向上しました。
パフォーマンスをさらに向上させるために、いくつかのループのアンロールを実行できます。これにより、プロセッサーのより多くのレジスターにデータがロードされます。つまり、ロード/ストア命令をインターリーブし、追加の命令(ループカウントなど)によってレイテンシを隠すことができます。ロード/ストア命令のレイテンシはまったく異なる可能性があるため、これによってもたらされる利点はプロセッサによってかなり異なります。
この段階では、コードをC(またはC ++)ではなくアセンブリで記述します。これは、ロードおよびストア命令を手動で配置して、レイテンシの隠蔽とスループットを最大限に引き出す必要があるためです。
通常、データのキャッシュライン全体を、展開されたループの1回の反復でコピーする必要があります。
これにより、プリフェッチを追加して、次の改善が可能になります。これらは、プロセッサのキャッシュシステムにメモリの特定の部分をキャッシュにロードするように指示する特別な命令です。命令を発行してからキャッシュラインが満たされるまでに遅延があるため、データをコピーするときにデータを利用できるように、命令を配置する必要があります。
これは、プリフェッチ命令を関数の開始時とメインコピーループ内に置くことを意味します。コピーループの途中にあるプリフェッチ命令を使用して、数回の反復でコピーされるデータをフェッチします。
思い出せませんが、送信元アドレスだけでなく宛先アドレスもプリフェッチするとよいでしょう。
要因
メモリのコピー速度に影響する主な要因は次のとおりです。
- プロセッサ、そのキャッシュ、およびメインメモリ間のレイテンシ。
- プロセッサのキャッシュラインのサイズと構造。
- プロセッサのメモリ移動/コピー命令(レイテンシ、スループット、レジスタサイズなど)。
したがって、効率的で高速なメモリ処理ルーチンを作成する場合は、作成するプロセッサとアーキテクチャについてかなりの知識が必要になります。言うまでもありませんが、組み込みプラットフォームで書いているのでなければ、組み込みのメモリコピールーチンを使用する方がはるかに簡単です。