等號賦值與memcpy的效率問題(轉(zhuǎn))

轉(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市役电,隨后出現(xiàn)的幾起案子赚爵,更是在濱河造成了極大的恐慌,老刑警劉巖法瑟,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冀膝,死亡現(xiàn)場離奇詭異,居然都是意外死亡霎挟,警方通過查閱死者的電腦和手機窝剖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酥夭,“玉大人赐纱,你說我怎么就攤上這事“颈保” “怎么了疙描?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蒜埋。 經(jīng)常有香客問我淫痰,道長,這世上最難降的妖魔是什么整份? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任待错,我火速辦了婚禮籽孙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘火俄。我一直安慰自己犯建,他們只是感情好,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布瓜客。 她就那樣靜靜地躺著适瓦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谱仪。 梳的紋絲不亂的頭發(fā)上玻熙,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機與錄音疯攒,去河邊找鬼嗦随。 笑死,一個胖子當著我的面吹牛敬尺,可吹牛的內(nèi)容都是我干的枚尼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼砂吞,長吁一口氣:“原來是場噩夢啊……” “哼署恍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蜻直,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤盯质,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后袭蝗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唤殴,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年到腥,在試婚紗的時候發(fā)現(xiàn)自己被綠了朵逝。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡乡范,死狀恐怖配名,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晋辆,我是刑警寧澤渠脉,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站瓶佳,受9級特大地震影響芋膘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一为朋、第九天 我趴在偏房一處隱蔽的房頂上張望臂拓。 院中可真熱鬧,春花似錦习寸、人聲如沸胶惰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽孵滞。三九已至,卻和暖如春鸯匹,著一層夾襖步出監(jiān)牢的瞬間坊饶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工忽你, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留幼东,地道東北人臂容。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓科雳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親脓杉。 傳聞我的和親對象是個殘疾皇子糟秘,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容