本文主要介绍下memcpy()的工作原理,其中采用的优化方法以及思路值得学习。
以下为glibc2.5中的memcpy的源码:
void *
memcpy (void* dstpp, const void* srcpp, size_t len)
{
unsigned long int dstp = (long int) dstpp;
unsigned long int srcp = (long int) srcpp;
//如果需要拷贝的字节数大于临界值,则会使用优化方法进行拷贝
if (len >= OP_T_THRES) //根据不同的情况,OP_T_THRES定义为16或8
{
len -= (-dstp) % OPSIZ; //小技巧,很值得学习
BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ); //按照字节进行对齐
PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len); //对于特殊平台可能使用虚拟页拷贝
WORD_COPY_FWD (dstp, srcp, len, len); //大字节拷贝
}
BYTE_COPY_FWD (dstp, srcp, len);
return dstpp;
}
整个memcpy的流程为:
1. 判断需要拷贝的字节数是否大于某一临界值。如果大于临界值,则可以使用更加强大的优化手段进行拷贝。否则,直接转6。
2. 假设要拷贝的目的地如下所示:
其中start为拷贝目的地的起始地址,end为拷贝目的地的结束地址,align border为内存中的对齐边界。在大多数平台下,从内存对齐边界开始拷贝会有许多的优化方法可以使用,此处memcpy正是利用了这点。
3. 计算start到align border的距离,此处使用了一个非常聪明的小技巧。使用 (-dstp) % OPSIZ 来计算start到align border的距离,这样可以减少一次判断。然后使用字节拷贝的方法来拷贝start到align border之间的内存。
4. 对于特殊平台,可能使用page copy的方法。由于限制条件较多,一般x86平台下不会使用。
5. 使用word copy的方法进行字节块拷贝,此处是memcpy优化的关键,优化的条件是拷贝地址处于对齐边界。在pentium系列平台和非pentium系列平台下,word copy有两种实现方式。
6. 剩余的不能采用word copy的尾部使用字节拷贝。
以下为x86平台下字节拷贝和字节块拷贝的实现
字节拷贝的实现:
#define BYTE_COPY_FWD(dst_bp, src_bp, nbytes) /
do { /
int __d0; /
asm volatile(/* Clear the direction flag, so copying goes forward. */ /
"cld/n" /
/* Copy bytes. */ /
"rep/n" /
"movsb" : /
"=D" (dst_bp), "=S" (src_bp), "=c" (__d0) : /
"0" (dst_bp), "1" (src_bp), "2" (nbytes) : /
"memory"); /
} while (0)
没啥好说的,利用x86的movsb指令实现字节拷贝。使用movsb指令时,需设置EDI,ESI,ECX寄存器的值,EDI寄存器存放拷贝的目的地 址,ESI寄存器存放拷贝的源地址,ECX为需要拷贝的字节数。拷贝完成之后,EDI中的值会保存到dst_bp中,ESI中的值会保存到src_bp 中。这也是为什么memcpy中没有出现对dst_bp操作的原因。
非Pentium平台下的word copy的实现:
#define WORD_COPY_FWD(dst_bp, src_bp, nbytes_left, nbytes) /
do /
{ /
int __d0; /
asm volatile(/* Clear the direction flag, so copying goes forward. */ /
"cld/n" /
/* Copy longwords. */ /
"rep/n" /
"movsl" : /
"=D" (dst_bp), "=S" (src_bp), "=c" (__d0) : /
"0" (dst_bp), "1" (src_bp), "2" ((nbytes) / 4) : /
"memory"); /
(nbytes_left) = (nbytes) % 4; /
} while (0)
利用x86的movsl指令实现四字节拷贝。如果movsl和movsb花费相同的cpu时钟周期,那优化后的拷贝时间将是原来的四分之一。恩,相当可观了。。。
Pentium平台下的word copy的实现:
#define WORD_COPY_FWD(dst_bp, src_bp, nbytes_left, nbytes) /
do /
{ /
asm volatile ("subl $32,%2/n" /
"js 2f/n" /
"movl 0(%0),%%edx/n" /* alloc dest line */ /
"1:/n" /
"movl 28(%0),%%eax/n" /* alloc dest line */ /
"subl $32,%2/n" /* decr loop count */ /
"movl 0(%1),%%eax/n" /* U pipe */ /
"movl 4(%1),%%edx/n" /* V pipe */ /
"movl %%eax,0(%0)/n" /* U pipe */ /
"movl %%edx,4(%0)/n" /* V pipe */ /
"movl 8(%1),%%eax/n" /
"movl 12(%1),%%edx/n" /
"movl %%eax,8(%0)/n" /
"movl %%edx,12(%0)/n" /
"movl 16(%1),%%eax/n" /
"movl 20(%1),%%edx/n" /
"movl %%eax,16(%0)/n" /
"movl %%edx,20(%0)/n" /
"movl 24(%1),%%eax/n" /
"movl 28(%1),%%edx/n" /
"movl %%eax,24(%0)/n" /
"movl %%edx,28(%0)/n" /
"leal 32(%1),%1/n" /* update src ptr */ /
"leal 32(%0),%0/n" /* update dst ptr */ /
"jns 1b/n" /
"2: addl $32,%2" : /
"=r" (dst_bp), "=r" (src_bp), "=r" (nbytes_left) : /
"0" (dst_bp), "1" (src_bp), "2" (nbytes) : /
"ax", "dx"); /
} while (0)
字节块单元的大小变为了32。在执行过程中,利用Pentium平台下的pipeline技术。此处光看代码可能感觉不出来优化,但是联想一下Pentium平台下强大的流水线技术就会发现以上指令中的许多工作都可以并发执行,执行效率会大大提高。
参考来源: http://blog.youkuaiyun.com/yalizhi123/article/details/5803503