memcpy是如何优化拷贝的

本文主要介绍下memcpy()的工作原理,其中采用的优化方法以及思路值得学习。


以下为glibc2.5中的memcpy的源码:

void *
memcpy (dstpp, srcpp, len)
     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平台下强大的流水线技术就会发现以上指令中的许多工作都可以并发执行,执行效率会大大提高。

我的观点是:或许可以使用Pentium平台下的SSE指令和MMX指令实现更好的优化。估计较新的glibc应该包含了(那群牛人,啥事都能干出来)。

通过分析代码,发现memcpy真不是盖的,浏览代码的过程中处处充满了惊喜。glibc的冰山一角即有如此的华丽的表现,想象一下整个库,不禁对glibc库的作者又添了几分敬仰之情。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值