偶然間看到一個叫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。當然,具體怎麼做是編譯器決定的。)