内存复制例程比通过指针的简单内存复制要复杂得多,而且速度更快:
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++;
}
改进之处
可以做的第一个改进是将一个指针对齐到字边界上(按字我的意思是本机整数大小,通常为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 ++)语言编写。
通常,应在展开循环的一次迭代中复制整个缓存行数据。
这带来了我的下一个改进,增加了预取。这些是特殊的指令,它们告诉处理器的缓存系统将内存的特定部分加载到其缓存中。由于在发出指令与填充高速缓存行之间存在延迟,因此需要以这样的方式放置指令,以便在复制数据时就可以使用该数据,并且不再早/晚。
这意味着将预取指令放在函数的开头以及主复制循环中。通过复制循环中间的预取指令,可以获取将在多个迭代时间内复制的数据。
我不记得了,但是预取目标地址和源地址也可能会有所帮助。
因素
影响可以快速复制内存的主要因素有:
- 处理器,其缓存和主内存之间的等待时间。
- 处理器的缓存行的大小和结构。
- 处理器的内存移动/复制指令(等待时间,吞吐量,寄存器大小等)。
因此,如果您想编写一个高效,快速的内存处理例程,则需要了解很多有关要编写的处理器和体系结构的信息。可以说,除非您在某个嵌入式平台上进行编写,否则仅使用内置的内存复制例程会容易得多。