轉(zhuǎn)http://blog.csdn.net/pngynghay/article/details/17142401
偶爾看到一個說法末誓,說展运,小內(nèi)存的拷貝坎弯,使用等號直接賦值比memcpy快得多透硝。結(jié)合自己搜集到的資料,整理成此文辛润。
事實:strcpy等函數(shù)的逐字節(jié)拷貝,memcpy是按照機器字長逐字進行拷貝的见秤,一個字等于4(32位機)或8(64位機)個字節(jié)砂竖。CPU存取一個字節(jié)和存取一個字一樣,都是在一條指令鹃答、一個內(nèi)存周期內(nèi)完成的晦溪。顯然,按字拷貝效率更高挣跋。
先給出一個程序:
[cpp]view plaincopy
#include?
#define?TESTSIZE????????128
structnode?{
charbuf[TESTSIZE];
};
intmain()
{
charsrc[TESTSIZE]?=?{0};
chardst[TESTSIZE];
*(structnode*)dst?=?*(structnode*)src;
}
編譯:gcc -g -o test test.c
獲得匯編:objdump -S test
可以看到有這么一些匯編三圆,對應的是等號賦值操作:
*(struct node*)dst = *(struct node*)src;
4004b6:?48 8d 85 00 ff ff ff ?lea??? 0xffffffffffffff00(%rbp),%rax
4004bd:?48 8d 55 80????????? ?lea??? 0xffffffffffffff80(%rbp),%rdx
4004c1:?48 8b 0a???????????? ?mov??? (%rdx),%rcx
4004c4:?48 89 08???????????? ?mov??? %rcx,(%rax)
4004c7:?48 8b 4a 08????????? ?mov??? 0x8(%rdx),%rcx
4004cb:?48 89 48 08????????? ?mov??? %rcx,0x8(%rax)
4004cf:?48 8b 4a 10????????? ?mov??? 0x10(%rdx),%rcx
4004d3:?48 89 48 10????????? ?mov??? %rcx,0x10(%rax)
4004d7:?48 8b 4a 18????????? ?mov??? 0x18(%rdx),%rcx
4004db:?48 89 48 18????????? ?mov??? %rcx,0x18(%rax)
4004df:?48 8b 4a 20????????? ?mov??? 0x20(%rdx),%rcx
4004e3:?48 89 48 20????????? ?mov??? %rcx,0x20(%rax)
4004e7:?48 8b 4a 28????????? ?mov??? 0x28(%rdx),%rcx
4004eb:?48 89 48 28????????? ?mov??? %rcx,0x28(%rax)
4004ef:?48 8b 4a 30????????? ?mov??? 0x30(%rdx),%rcx
4004f3:?48 89 48 30????????? ?mov??? %rcx,0x30(%rax)
4004f7:?48 8b 4a 38????????? ?mov??? 0x38(%rdx),%rcx
4004fb:?48 89 48 38????????? ?mov??? %rcx,0x38(%rax)
4004ff:?48 8b 4a 40????????? ?mov??? 0x40(%rdx),%rcx
400503:?48 89 48 40????????? ?mov??? %rcx,0x40(%rax)
400507:?48 8b 4a 48????????? ?mov??? 0x48(%rdx),%rcx
40050b:?48 89 48 48????????? ?mov??? %rcx,0x48(%rax)
40050f:?48 8b 4a 50????????? ?mov??? 0x50(%rdx),%rcx
400513:?48 89 48 50????????? ?mov??? %rcx,0x50(%rax)
400517:?48 8b 4a 58????????? ?mov??? 0x58(%rdx),%rcx
40051b:?48 89 48 58????????? ?mov??? %rcx,0x58(%rax)
40051f:?48 8b 4a 60????????? ?mov??? 0x60(%rdx),%rcx
400523:?48 89 48 60????????? ?mov??? %rcx,0x60(%rax)
400527:?48 8b 4a 68????????? ?mov??? 0x68(%rdx),%rcx
40052b:?48 89 48 68????????? ?mov??? %rcx,0x68(%rax)
40052f:?48 8b 4a 70????????? ?mov??? 0x70(%rdx),%rcx
400533:?48 89 48 70????????? ?mov??? %rcx,0x70(%rax)
400537:?48 8b 52 78????????? ?mov??? 0x78(%rdx),%rdx
40053b:?48 89 50 78????????? ?mov??? %rdx,0x78(%rax)
獲得libc的memcpy匯編代碼:objdump -S /lib/libc.so.6
00973a30 :
973a30:?????? 8b 4c 24 0c???????????? mov??? 0xc(%esp),%ecx
973a34:?????? 89 f8?????????????????? mov??? %edi,%eax
973a36:?????? 8b 7c 24 04???????????? mov??? 0x4(%esp),%edi
973a3a:?????? 89 f2?????????????????? mov??? %esi,%edx
973a3c:?????? 8b 74 24 08???????????? mov??? 0x8(%esp),%esi
973a40:?????? fc????????????????????? cld
973a41:?????? d1 e9?????????????????? shr??? %ecx
973a43:?????? 73 01?????????????????? jae??? 973a46
973a45:?????? a4????????????????????? movsb? %ds:(%esi),%es:(%edi)
973a46:?????? d1 e9?????????????????? shr??? %ecx
973a48:?????? 73 02?????????????????? jae??? 973a4c
973a4a:?????? 66 a5?????????????????? movsw? %ds:(%esi),%es:(%edi)
973a4c:?????? f3 a5?????????????????? rep movsl %ds:(%esi),%es:(%edi)
973a4e:?????? 89 c7?????????????????? mov??? %eax,%edi
973a50:?????? 89 d6?????????????????? mov??? %edx,%esi
973a52:?????? 8b 44 24 04???????????? mov??? 0x4(%esp),%eax
973a56:?????? c3????????????????????? ret
973a57:?????? 90????????????????????? nop
原來兩者都是通過逐字拷貝來實現(xiàn)的。但是“等號賦值”被編譯器翻譯成一連串的MOV指令避咆,而memcpy則是一個循環(huán)舟肉。“等號賦值”比memcpy快查库,并不是快在拷貝方式上路媚,而是快在程序流程上。
測試發(fā)現(xiàn)樊销,“等號賦值”的長度必須小于等于128整慎,并且是機器字長的倍數(shù)脏款,才會被編譯成連續(xù)MOV形式,否則會被編譯成調(diào)用memcpy裤园。而同樣的撤师,如果memcpy復制的長度小于等于128且是機器字長的整數(shù)倍,會被編譯成MOV形式拧揽。所以剃盾,無論你的代碼中如何寫,編譯器都會做好優(yōu)化工作淤袜。
而為什么同樣是按機器字長拷貝痒谴,連續(xù)的MOV指令就要比循環(huán)MOV快呢?
在循環(huán)方式下铡羡,每一次MOV過后积蔚,需要:1、判斷是否拷貝完成烦周;2尽爆、跳轉(zhuǎn)以便繼續(xù)拷貝。
循環(huán)還是比較浪費的论矾。如果效率要求很高教翩,很多情況下,我們需要把循環(huán)展開(比如在本例中贪壳,每次循環(huán)拷貝N個字節(jié))饱亿,以避免判斷與跳轉(zhuǎn)占用大量的CPU時間。這算是一種以空間換時間的做法闰靴。GCC就有自動將循環(huán)展開的編譯選項(如:-funroll-loops)彪笼。
循環(huán)展開也是應該有個度的,并不是越展開越好(即使不考慮對空間的浪費)蚂且。因為CPU的快速執(zhí)行很依賴于cache配猫,如果cache不命中,CPU將浪費不少的時鐘周期在等待內(nèi)存上(內(nèi)存的速度一般比CPU低一個數(shù)量級)杏死。而小段循環(huán)結(jié)構(gòu)就比較有利于cache命中泵肄,因為重復執(zhí)行的一段代碼很容易被硬件放在cache中,這就是代碼局部性帶來的好處淑翼。而過度的循環(huán)展開就打破了代碼的局部性腐巢。如果要拷貝的字節(jié)更多,則全部展開成連續(xù)的MOV指令的做法未必會很高效玄括。
綜上所述冯丙,“等號賦值”之所以比memcpy快,就是因為它省略了CPU對于判斷與跳轉(zhuǎn)的處理遭京,消除了分支對CPU流水的影響胃惜。而這一切都是通過適度展開內(nèi)存拷貝的循環(huán)來實現(xiàn)的泞莉。
如果將libc的memcpy換成時等號循環(huán)賦值,效率會如何船殉,程序如下timememcpy.c:
[cpp]view plaincopy
#include?
#include?
#include?
#include?
#define?LEN?0x20000
#define?MYM?1
#define?LIBM?0
char*dst;
char*src;
typedefstructmemcpy_data_size
{
inta[16];
}DATA_SIZE,?*P_DATA_SIZE;
void*mymemcpy(void*to,constvoid*from,size_tsize)
{
P_DATA_SIZE?dst?=?(P_DATA_SIZE)to;
P_DATA_SIZE?src?=?(P_DATA_SIZE)from;
intnew_len??=?size/sizeof(DATA_SIZE)-1;
intremain??=?size%sizeof(DATA_SIZE)-1;
while(new_len?>=?1)
{
*dst++?=?*src++;
new_len--;
}
#if?0
while(new_len?>=?2)
{
*dst++?=?*src++;
*dst++?=?*src++;
new_len?=?new_len?-2;
}
if(new_len?==?1)
{
*dst++?=?*src++;
}
#endif
while(remain?>=?0)
{
*((char*)dst?+?remain)?=?*((char*)src?+?remain);
remain--;
}
returnto;
}
intmain(intargc,charconst*?argv[])
{
inttype?=?0;
structtimeval?start,?end;
unsignedlongdiff;
gettimeofday(&start,?NULL);
if(argc?!=?2){
printf("you?should?run?it?as?:?./run?1(or?0)\n");
printf("1:?run?my?memcpy\n");
printf("0:?run?lib?memcpy\n");
exit(0);
}
type?=?atoi(argv[1]);
if(MYM?!=?type?&&?LIBM?!=?type){
printf("you?should?run?it?as?:?./run?1(or?0)\n");
printf("1:?run?my?memcpy\n");
printf("0:?run?lib?memcpy\n");
exit(0);
}
dst?=?malloc(sizeof(char)*LEN);
if(NULL?==?dst)?{
perror("dst?malloc");
exit(1);
}
src?=?malloc(sizeof(char)*LEN);
if(NULL?==?src)?{
perror("src?malloc");
exit(1);
}
if(MYM?==?type){
mymemcpy(dst,?src,?LEN);
printf("my?memcpy:\n");
}
else{
memcpy(dst,?src,?LEN);
printf("lib?memcpy:\n");
}
free(dst);
free(src);
gettimeofday(&end,?NULL);
diff?=?1000000*(end.tv_sec?-?start.tv_sec)+?end.tv_usec?-?start.tv_usec;
printf("run?time?is?%ld?us\n",diff);
return0;
}
被注釋掉的幾行代碼本來是用來循環(huán)展開的鲫趁,可測試結(jié)果并沒發(fā)現(xiàn)有什么好處,故捺弦,先注釋掉饮寞。
在測試程序中孝扛,經(jīng)過多次測試列吼,并無法真正確定libc和自己實現(xiàn)的memcpy效率誰優(yōu)誰劣】嗍迹可能是由于運行時間太短以及進程調(diào)度所致寞钥。
目前為止,還沒找到更好的測試方法陌选,去驗證效率的優(yōu)劣理郑。
現(xiàn)將我的測試數(shù)據(jù)粘貼至此,僅供參考:
編譯程序:gcc -g -o timememcpy timememcpy.c
執(zhí)行測試腳本為:run.sh
[python]view plaincopy
#!/bin/sh
./timememcpy1
./timememcpy1
./timememcpy1
./timememcpy1
./timememcpy1
./timememcpy0
./timememcpy0
./timememcpy0
./timememcpy0
./timememcpy0
運行該腳本咨油,得結(jié)果如下:
[root@SPA c]# ./run.sh
my memcpy:
run time is 435 us
my memcpy:
run time is 237 us
my memcpy:
run time is 249 us
my memcpy:
run time is 304 us
my memcpy:
run time is 300 us
lib memcpy:
run time is 262 us
lib memcpy:
run time is 222 us
lib memcpy:
run time is 335 us
lib memcpy:
run time is 281 us
lib memcpy:
run time is 247 us
腳本內(nèi)容修改為:
[python]view plaincopy
#!/bin/sh
./timememcpy0
./timememcpy0
./timememcpy0
./timememcpy0
./timememcpy0
./timememcpy1
./timememcpy1
./timememcpy1
./timememcpy1
./timememcpy1
再次運行您炉,得結(jié)果:
[root@SPA c]# ./run.sh
lib memcpy:
run time is 479 us
lib memcpy:
run time is 461 us
lib memcpy:
run time is 512 us
lib memcpy:
run time is 405 us
lib memcpy:
run time is 365 us
my memcpy:
run time is 399 us
my memcpy:
run time is 314 us
my memcpy:
run time is 309 us
my memcpy:
run time is 510 us
my memcpy:
run time is 324 us