天天看點

比memcpy更快的記憶體拷貝

     偶然間看到一個叫xmemcpy的工具,用做記憶體拷貝。号稱在拷貝120位元組以内時,比glibc提供的memcpy快10倍,并且有實驗資料。

     這讓人感覺很詫異。一直以來都覺得memcpy是很高效的。相比于strcpy等函數的逐位元組拷貝,memcpy是按照機器字長逐字進行拷貝的,一個字等于4(32位機)或8(64位機)個位元組。CPU存取一個位元組和存取一個字一樣,都是在一條指令、一個記憶體周期内完成的。顯然,按字拷貝效率更高。

     那麼,這個xmemcpy是靠什麼來實作比memcpy“快10倍”的呢?

     看了一下xmemcpy的實作,原來它速度快的根據是:“小記憶體的拷貝,使用等号直接指派比memcpy快得多”。

     這下就更納悶了,記憶體拷貝不就是把一塊記憶體一部分一部分地拷貝到另一塊記憶體去嗎?難道逐字拷貝還有性能提升的空間?

寫了一段代碼:

#include <stdio.h>

#define TESTSIZE        128

struct node {

char buf[TESTSIZE];

};

void main()

{

char src[TESTSIZE] = {0};

char dst[TESTSIZE];

*(struct node*)dst = *(struct node*)src;

}

然後反彙編:

......

00000000004004a8 <main>:

4004a8:       55                      push   %rbp

4004a9:       48 89 e5                mov    %rsp,%rbp

4004ac:       48 81 ec 00 01 00 00    sub    $0x100,%rsp

4004b3:       48 8d 7d 80             lea    0xffffffffffffff80(%rbp),%rdi

4004b7:       ba 80 00 00 00          mov    $0x80,%edx

4004bc:       be 00 00 00 00          mov    $0x0,%esi

4004c1:       e8 1a ff ff ff          callq 4003e0 <>

4004c6:       48 8b 45 80             mov    0xffffffffffffff80(%rbp),%rax

4004ca:       48 89 85 00 ff ff ff    mov    %rax,0xffffffffffffff00(%rbp)

4004d1:       48 8b 45 88             mov    0xffffffffffffff88(%rbp),%rax

400564:       48 89 85 70 ff ff ff    mov    %rax,0xffffffffffffff70(%rbp)

40056b:       48 8b 45 f8             mov    0xfffffffffffffff8(%rbp),%rax

40056f:       48 89 85 78 ff ff ff    mov    %rax,0xffffffffffffff78(%rbp)

400576:       c9                      leaveq 

400577:       c3                      retq   

400578:       90                      nop    

再将libc反彙編,并找到memcpy的實作,以作比較:

0006b400 <memcpy>:

6b400:       8b 4c 24 0c             mov    0xc(%esp),%ecx

6b404:       89 f8                   mov    %edi,%eax

6b406:       8b 7c 24 04             mov    0x4(%esp),%edi

6b40a:       89 f2                   mov    %esi,%edx

6b40c:       8b 74 24 08             mov    0x8(%esp),%esi

6b410:       fc                      cld    

6b411:       d1 e9                   shr    %ecx

6b413:       73 01                   jae    6b416 <memcpy+0x16>

6b415:       a4                      movsb %ds:(%esi),%es:(%edi)

6b416:       d1 e9                   shr    %ecx

6b418:       73 02                   jae    6b41c <memcpy+0x1c>

6b41a:       66 a5                   movsw %ds:(%esi),%es:(%edi)

6b41c:       f3 a5                   repz movsl %ds:(%esi),%es:(%edi)

6b41e:       89 c7                   mov    %eax,%edi

6b420:       89 d6                   mov    %edx,%esi

6b422:       8b 44 24 04             mov    0x4(%esp),%eax

6b426:       c3                      ret    

6b427:       90                      nop    

      原來兩者都是通過逐字拷貝來實作的。但是“等号指派”被編譯器翻譯成一連串的MOV指令,而memcpy則是一個循環。“等号指派”比memcpy快,并不是快在拷貝方式上,而是快在程式流程上。

   (另外,測試發現,“等号指派”的長度必須小于等于128,并且是機器字長的倍數,才會被編譯成連續MOV形式,否則會被編譯成調用memcpy。當然,具體怎麼做是編譯器決定的。)