電腦的儲存結構是層次性的,從快到慢,代價從高到低,容量從小到大,寄存器,L1 cache,L2 cache,直到磁碟,甚至比磁碟更慢速的磁帶機,因此在程式運行時,不可避免的會有複製,這一點很重要,從最佳化的角度看,很多時候都是為了減少複製的代價,比如如果已知磁碟的讀寫是順序的,這樣採用DIRECTIO是較好的,直接讀到使用者記憶體上,而不需要先讀到核心記憶體上,然後再複製到使用者記憶體,這個例子我打算在未來的實驗中給出,當然DirectIO相當於程式員自己實現緩衝,在小規模資料讀寫上並沒有優勢,因為節省的這些複製開銷微乎其微。
本文通過減少cache line回填(也是一種複製),來說明減少複製獲得的收益。首先瞭解一些背景知識:
CPU通過晶片緩衝(L1 Cache)和記憶體進行資料互動。而互動的單位叫做cache line,大小為2的冪,32或64位元組,虛擬記憶體頁面大小為4KB。cache line的互動分為兩種回填(refill)和回寫(write-back)兩種。假定CPU需要從虛擬記憶體讀取一個位元組的運算元,其地址為0xFFFFFFA3,cache line的大小為32位元組,則CPU需要將地址0xFFFFFFA0地址開始的32個位元組全部讀入,填充出一個完整的cache line後,然後從該cache line的第4個位元組處取得該位元組的內容。如果後繼的指令也需要同樣從cache行中讀,那麼填充cache
line是值得的;否則,額外的填充cache line的時間就是浪費。因此指令和資料的局部性越好,越符合cache line的設計要求。
我們都非常熟悉的memset函數,如果我們手寫一個memset可能不如庫函數memset的實現效能高,為什麼呢?其中一個主要最佳化技術稱之為non-temporal,其基本思想是,如果要寫入的記憶體資料無用時,直接寫入,而不需要回填cache line。涉及的指令包括movnti,movntdq,sfence等。通俗點說,我們要將一個字元(char),memset在一片記憶體上,而這片記憶體上原有的資料顯然沒有用了,即不需要將這片資料的內容先refill進cacheline,在cacheline中改寫瞭然後再寫回記憶體,只需要直接將資料寫入記憶體即可。庫函數的memset使用的是通用寄存器,而不是SSE寄存器,使用了movnti指令,通過objdump指令可以將庫函數的memset代碼匯出,參見在本文的最後。
為什麼一定要refill呢,不refill不可以嗎?假定我們對一片記憶體寫入1個位元組(假定一條cache line是32位元組),資料互動的單位是cache Line,系統怎麼知道另外的31個位元組是什麼呢?這一寫回,這1個位元組是對的,另外的31個位元組就未定義了。因此小資料的讀寫refill是有必要的,但是,對於大片記憶體的寫入,顯然refill是可以避免的,intel提供的non-temperal方法也就是為這個目的服務的,即本文的減少複製的最佳化思想。
代碼中還有一些技巧不再詳述,如果有反饋,我再寫個續篇解答,詳細請參見下列代碼。
#include <stdlib.h><br />#include <string.h><br />#include <stdio.h><br />#include <iostream><br />typedef unsigned char __attribute__((aligned(16))) fill_t[16];<br />using namespace std;<br />void naive_memset(void *page,unsigned char fill, size_t count)<br />{<br /> unsigned char *dst = (unsigned char*)page;<br /> unsigned char *end = dst + count;<br /> for(;dst<end;)<br /> {<br /> *dst++ = fill;<br /> };<br />};</p><p>void my_memset(void *page, unsigned char fill, size_t count)<br />{<br /> unsigned char *dst = (unsigned char*)page;<br /> fill_t dfill;<br /> for(size_t i = 0;i<16;)<br /> {<br /> dfill[i++]=fill;<br /> }<br /> __asm__ __volatile__ (<br /> " movdqa (%0),%%xmm0/n"<br /> " movdqa %%xmm0,%%xmm1/n"<br /> " movdqa %%xmm0,%%xmm2/n"<br /> " movdqa %%xmm0,%%xmm3/n"<br /> " movdqa %%xmm0,%%xmm4/n"<br /> " movdqa %%xmm0,%%xmm5/n"<br /> " movdqa %%xmm0,%%xmm6/n"<br /> " movdqa %%xmm0,%%xmm7/n"<br /> :: "r"(dfill)<br /> );<br /> while (((long)dst & 0xF) && (count > 0)) {<br /> *dst++ = fill;<br /> count--;<br /> }<br /> size_t m_loop = count/128;<br /> size_t r = count%128;<br /> for(size_t i=0;i<m_loop;++i)<br /> {<br /> __asm__ (<br /> " movntdq %%xmm0, (%0)/n"<br /> " movntdq %%xmm1, 16(%0)/n"<br /> " movntdq %%xmm2, 32(%0)/n"<br /> " movntdq %%xmm3, 48(%0)/n"<br /> " movntdq %%xmm4, 64(%0)/n"<br /> " movntdq %%xmm5, 80(%0)/n"<br /> " movntdq %%xmm6, 96(%0)/n"<br /> " movntdq %%xmm7, 112(%0)/n"<br /> ::"r" (dst) :"memory" );<br /> dst+=128;<br /> }<br /> for(int i=0;i<r;++i)<br /> {<br /> *dst++ = fill;<br /> }<br /> __asm__ __volatile__ (<br /> " sfence /n "<br /> ::<br /> );<br />};<br />#if defined(__i386__)<br />static __inline__ unsigned long long rdtsc(void)<br />{<br /> unsigned long long int x;<br /> __asm__ volatile ("rdtsc" : "=A" (x));<br /> return x;<br />}<br />#elif defined(__x86_64__)<br />static __inline__ unsigned long long rdtsc(void)<br />{<br /> unsigned hi, lo;<br /> __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));<br /> return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );<br />}<br />#endif<br />int main()<br />{<br /> const size_t s = 40*1024*1024;<br /> void* p = malloc(s);<br /> memset(p,0x0,s);<br /> unsigned long long start = rdtsc();<br /> unsigned char*q=(unsigned char*)p;<br /> #ifdef _NAIVE_MEM<br /> naive_memset(p,0x1,s);<br /> #endif<br /> #ifdef _MY_MEM<br /> my_memset(p,0x1,s);<br /> #endif<br /> #ifdef _MEM<br /> memset(p,0x1,s);<br /> #endif<br /> int sum = 0;<br /> for(int i=0;i<s-1;++i)<br /> {<br /> sum += *q;<br /> ++q;<br /> }<br /> cout<<"sum:"<<sum<<endl;<br /> cout<<"run time:"<<rdtsc()-start<<endl;<br /> free(p);<br /> return 0;<br />}</p><p>
glibc的庫函數memset的彙編代碼:
0000003fb6279540 <memset>:
3fb6279540: 48 83 fa 07 cmp $0x7,%rdx
3fb6279544: 48 89 f9 mov %rdi,%rcx
3fb6279547: 0f 86 96 00 00 00 jbe 3fb62795e3 <memset+0xa3>
3fb627954d: 49 b8 01 01 01 01 01 mov $0x101010101010101,%r8
3fb6279554: 01 01 01
3fb6279557: 40 0f b6 c6 movzbl %sil,%eax
3fb627955b: 4c 0f af c0 imul %rax,%r8
3fb627955f: f7 c7 07 00 00 00 test $0x7,%edi
3fb6279565: 74 1a je 3fb6279581 <memset+0x41>
3fb6279567: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
3fb627956e: 00 00
3fb6279570: 40 88 31 mov %sil,(%rcx)
3fb6279573: 48 ff ca dec %rdx
3fb6279576: 48 ff c1 inc %rcx
3fb6279579: f7 c1 07 00 00 00 test $0x7,%ecx
3fb627957f: 75 ef jne 3fb6279570 <memset+0x30>
3fb6279581: 48 89 d0 mov %rdx,%rax
pennyliang5: 48 c1 e8 06 shr $0x6,%rax
3fb6279588: 74 3e je 3fb62795c8 <memset+0x88>
3fb627958a: 48 81 fa c0 d4 01 00 cmp $0x1d4c0,%rdx
3fb6279591: 73 6d jae 3fb6279600 <memset+0xc0>
3fb6279593: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
3fb6279599: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
3fb62795a0: 4c 89 01 mov %r8,(%rcx)
3fb62795a3: 4c 89 41 08 mov %r8,0x8(%rcx)
3fb62795a7: 4c 89 41 10 mov %r8,0x10(%rcx)
3fb62795ab: 4c 89 41 18 mov %r8,0x18(%rcx)
3fb62795af: 4c 89 41 20 mov %r8,0x20(%rcx)
3fb62795b3: 4c 89 41 28 mov %r8,0x28(%rcx)
3fb62795b7: 4c 89 41 30 mov %r8,0x30(%rcx)
3fb62795bb: 4c 89 41 38 mov %r8,0x38(%rcx)
3fb62795bf: 48 83 c1 40 add $0x40,%rcx
3fb62795c3: 48 ff c8 dec %rax
3fb62795c6: 75 d8 jne 3fb62795a0 <memset+0x60>
3fb62795c8: 83 e2 3f and $0x3f,%edx
3fb62795cb: 48 89 d0 mov %rdx,%rax
3fb62795ce: 48 c1 e8 03 shr $0x3,%rax
3fb62795d2: 74 0c je 3fb62795e0 <memset+0xa0>
3fb62795d4: 4c 89 01 mov %r8,(%rcx)
3fb62795d7: 48 83 c1 08 add $0x8,%rcx
3fb62795db: 48 ff c8 dec %rax
3fb62795de: 75 f4 jne 3fb62795d4 <memset+0x94>
3fb62795e0: 83 e2 07 and $0x7,%edx
3fb62795e3: 48 85 d2 test %rdx,%rdx
3fb62795e6: 74 0b je 3fb62795f3 <memset+0xb3>
3fb62795e8: 40 88 31 mov %sil,(%rcx)
3fb62795eb: 48 ff c1 inc %rcx
3fb62795ee: 48 ff ca dec %rdx
3fb62795f1: 75 f5 jne 3fb62795e8 <memset+0xa8>
3fb62795f3: 48 89 f8 mov %rdi,%rax
3fb62795f6: c3 retq
3fb62795f7: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
3fb62795fe: 00 00
3fb6279600: 4c 0f c3 01 movnti %r8,(%rcx)
3fb6279604: 4c 0f c3 41 08 movnti %r8,0x8(%rcx)
3fb6279609: 4c 0f c3 41 10 movnti %r8,0x10(%rcx)
3fb627960e: 4c 0f c3 41 18 movnti %r8,0x18(%rcx)
3fb6279613: 4c 0f c3 41 20 movnti %r8,0x20(%rcx)
3fb6279618: 4c 0f c3 41 28 movnti %r8,0x28(%rcx)
3fb627961d: 4c 0f c3 41 30 movnti %r8,0x30(%rcx)
3fb6279622: 4c 0f c3 41 38 movnti %r8,0x38(%rcx)
3fb6279627: 48 83 c1 40 add $0x40,%rcx
3fb627962b: 48 ff c8 dec %rax
3fb627962e: 75 d0 jne 3fb6279600 <memset+0xc0>
3fb6279630: 0f ae f8 sfence
3fb6279633: eb 93 jmp 3fb62795c8 <memset+0x88>
3fb6279635: 90 nop
3fb6279636: 90 nop
3fb6279637: 90 nop
3fb6279638: 90 nop
3fb6279639: 90 nop
3fb627963a: 90 nop
3fb627963b: 90 nop
3fb627963c: 90 nop
3fb627963d: 90 nop
3fb627963e: 90 nop
3fb627963f: 90 nop
關於本部落格的一些後續話題,參見:
http://blog.csdn.net/pennyliang/archive/2011/01/18/6151062.aspx
http://blog.csdn.net/pennyliang/archive/2011/01/20/6154929.aspx