1.html
一直以來對數據對齊都不明白,直到看了這篇文章後才能說是有點感受,結合代碼將理解寫下來,以備未來回頭加深理解或者是修正錯誤的認知。linux
http://www.searchtb.com/2013/04/performance_optimization_tips.html程序員
代碼以下,運行的環境是64位的linux機器,內核3.10,gcc 4.8.2算法
/************************************************************************* > File Name: test_align.c > Author: dingzhengsheng > Mail: dingzs3@asiainfo.com > Created Time: 2015年04月22日 星期三 15時49分08秒 ************************************************************************/ #include<stdio.h> #include<stdint.h> #include<time.h> #include<stdlib.h> #include <unistd.h> #include<string.h> #include<sys/time.h> #include<sys/resource.h> #define SECTOUSEC 1000000 #define ARR_LEN 10000000 #define OP | //#define DZSTEST #ifdef DZSTEST #pragma pack (push) #pragma pack (1) struct notalignstruct { char a; char b; char c; uint32_t d; }; #pragma pack (pop) struct alignstruct { char a; char b; char c; uint32_t d; }; #else struct notalignstruct { char a; uint32_t d; short int c; }; struct alignstruct { char a; short int c; uint32_t d; }; #endif void case_one(struct notalignstruct *array, uint32_t array_len, uint32_t *sum) { uint32_t value = 1; uint32_t i; for(i=0; i<array_len; i++) value = value OP array[i].d; *sum = *sum OP value; } void case_two(struct alignstruct *array, uint32_t array_len, uint32_t *sum) { uint32_t value = 1; uint32_t i; for(i=0; i<array_len; i++) value = value OP array[i].d; *sum = *sum OP value; } void case_two_yh1(struct alignstruct *array, uint32_t array_len, uint32_t *sum) { uint32_t value = 1; uint32_t i; uint32_t length = array_len - (array_len&0x3); for(i=0; i<length; i+=4) { value = value OP array[i].d; value = value OP array[i+1].d; value = value OP array[i+2].d; value = value OP array[i+3].d; } for(; i<length; i+=4) value = value OP array[i].d; *sum = *sum OP value; } void case_two_yh2(struct alignstruct *array, uint32_t array_len, uint32_t *sum) { uint32_t value = 1; uint32_t i; uint32_t length = array_len - (array_len&0x3); uint32_t value1,value2; for(i=0; i<length; i+=4) { value1 = array[i].d OP array[i+1].d; value2 = array[i+2].d OP array[i+3].d; value = value1 OP value2; } for(; i<length; i+=4) value = value OP array[i].d; *sum = *sum OP value; } void case_two_yh3(struct alignstruct *array, uint32_t array_len, uint32_t *sum) { register uint32_t value = 1; register uint32_t i; uint32_t length = array_len - (array_len&0x3); uint32_t value1,value2; for(i=0; i<length; i+=4) { value1 = array[i].d OP array[i+1].d; value2 = array[i+2].d OP array[i+3].d; value = value1 OP value2; } for(; i<length; i+=4) value = value OP array[i].d; *sum = *sum OP value; } long int get_diff_time(struct timeval *tv1, struct timeval *tv2) { long int n; n = tv2->tv_sec*SECTOUSEC + tv2->tv_usec - tv1->tv_sec*SECTOUSEC - tv1->tv_usec; return n; } void main() { void *p; struct notalignstruct *array = malloc(sizeof(struct notalignstruct) * ARR_LEN); struct alignstruct *arr = malloc(sizeof(struct alignstruct) * ARR_LEN); uint32_t sum; struct timeval tv1,tv2; time_t timep; struct timezone tz; time(&timep); printf("數據對齊的影響比較:\n"); gettimeofday(&tv1,NULL); case_one(array, ARR_LEN, &sum); gettimeofday(&tv2,NULL); printf("sizeof(not)=%u : %ld\n",sizeof(struct notalignstruct),get_diff_time(&tv1, &tv2)); gettimeofday(&tv1,NULL); case_two(arr, ARR_LEN, &sum); gettimeofday(&tv2,NULL); printf("sizeof(not)=%u : %ld\n",sizeof(struct alignstruct),get_diff_time(&tv1, &tv2)); printf("代碼優化的性能比較:\n"); gettimeofday(&tv1,NULL); case_two_yh1(arr, ARR_LEN, &sum); gettimeofday(&tv2,NULL); printf("減小cpu循環分支預測數sizeof(align)=%u : %ld\n",sizeof(struct alignstruct),get_diff_time(&tv1, &tv2)); gettimeofday(&tv1,NULL); case_two_yh2(arr, ARR_LEN, &sum); gettimeofday(&tv2,NULL); printf("提升CPU指令流水線並行計算sizeof(align)=%u : %ld\n",sizeof(struct alignstruct),get_diff_time(&tv1, &tv2)); gettimeofday(&tv1,NULL); case_two_yh3(arr, ARR_LEN, &sum); gettimeofday(&tv2,NULL); printf("將高頻率的數據放入寄存器sizeof(align)=%u : %ld\n",sizeof(struct alignstruct),get_diff_time(&tv1, &tv2)); }
初始是想比較前兩種不一樣的對齊方式的性能差距數組
#pragma pack (push)
#pragma pack (1)緩存
struct notalignstruct
{
char a;
char b;
char c;
uint32_t d;
};
#pragma pack (pop)函數
struct alignstruct
{
char a;
char b;
char c;
uint32_t d;
};性能
原博文已經說的很清楚了,可是有一點我還有所懷疑就是若是編譯器對notalignstruct數組分配的內存的起始地址不是2的N次方怎麼辦,那麼對結構體中的變量d的訪問是否仍然會致使兩次取內存了,仍是說編譯器會自動解決這個問題嗎?優化
運行的結果是ui
數據對齊的影響比較:
sizeof(not)=7 : 43064
sizeof(not)=8 : 38102
代碼優化的性能比較:
減小cpu循環分支預測數sizeof(align)=8 : 26263
提升CPU指令流水線並行計算sizeof(align)=8 : 21551
將高頻率的數據放入寄存器sizeof(align)=8 : 14229
差距仍是挺大啊,而後再看這種狀況:
struct notalignstruct
{
char a;
uint32_t d;
short int c;
};
struct alignstruct
{
char a;
short int c;
uint32_t d;
};
這個很明顯,對齊的結果是前者會是12個字節的長度,後者是8個字節。程序運行前,我推測的結果是二者的速度應該是一致的,兩者均按照4個字節對齊了。
可是結果仍是出人意外:
數據對齊的影響比較:
sizeof(not)=12 : 40635
sizeof(not)=8 : 39052
代碼優化的性能比較:
減小cpu循環分支預測數sizeof(align)=8 : 26475
提升CPU指令流水線並行計算sizeof(align)=8 : 21788
將高頻率的數據放入寄存器sizeof(align)=8 : 14326
[ding@dzs bug_test]$ ./test
數據對齊的影響比較:
sizeof(not)=12 : 40594
sizeof(not)=8 : 38828
代碼優化的性能比較:
減小cpu循環分支預測數sizeof(align)=8 : 26262
提升CPU指令流水線並行計算sizeof(align)=8 : 21628
將高頻率的數據放入寄存器sizeof(align)=8 : 14332
前者的速度小於後者,難道是由於前者佔用的內存空間更大,實際物理內存跨頁,碎片化更厲害致使訪問更慢嗎?
而後針對後面這兩個結構體的計算函數case_two的優化就有意思了,博文中的這張圖片就能說得很清楚了。實際上也很好理解,假如十我的連成一排從貨車上已個個箱子裏取一包包的貨物傳遞到生產線上,而流水線的生產速度要大於工人傳遞的速度,而旁邊有個檢測師站在生成線的頭上,會對每一個箱子裏的頭一包貨物攔住檢查一下帳本,將已收貨物總數加1,並看是否在要用到的貨物限額以內,若是檢查到這包貨物質量優良,那麼整箱的貨物都免檢經過,不然就得丟掉這包貨物,同時10我的手頭上拿到的貨物也得丟掉,case_two的狀況就很明顯,一個箱子裏裏面就放了一包貨物,等於每個都得停下來檢查,你們都得在他檢查的時候等着。
在咱們本身就已經知道這個限額很大,是4XN 再加上零碎的三兩包就夠了,而咱們檢查的時候徹底能夠一個箱子裏多放4個,每次只在箱子裏第一包貨物時作技術檢查就能夠了。
那這樣就快不少了,流水線變得更加流暢了:
sizeof(not)=8 : 38828
代碼優化的性能比較:
減小cpu循環分支預測數sizeof(align)=8 : 26262
而後了,工人說包都很輕,能不能一次就拿兩個包來傳遞,貨物上得更快了,這樣讓流水線減小空轉的時間:
提升CPU指令流水線並行計算sizeof(align)=8 : 21628
而後檢測師說我也能更快一點,收包數就不記到帳本上一條一條的加了,我心頭默記這樣不久更快了嘛,免得翻帳本費時間:
將高頻率的數據放入寄存器sizeof(align)=8 : 14332
補充:再修改了一下,將length也改爲register變量,執行速度更快了,只須要13464。
2.
如今來總結上面的技術與原理:
數據對齊就不說了,分支預測問題,上述的例子for循環實際上並不能說明問題。我推測的是若是是1000000萬次for循環,循環體內是if(do something)else (do something)這種狀況下性能的影響會很是明顯。現代的cpu的分支預測能力已經很強了,像這種for循環的判斷,我以爲成功率應該是接近100%的,若是是if那種,且判斷的條件時刻都在變得狀況,分支預測的成功率就可能會降到50%左右吧。我不知道分支預測的原理與算法,我推想的是這就如同猜硬幣的正反同樣,可能cpu可以根據當前的風向,當前扔的力度來猜想,歷史數據,提升了準確度。內核代碼中的likely和unlikely就是程序員告訴編譯器哪一個分支預測的可能性更高,那麼編譯器就將可能性高的代碼編譯的指令放在判斷以後,訪問的更快。
至於指令的並行處理,個人理解是減小執行的機器代碼,取數據的動做仍是兩次,可是計算的動做已經只須要作一次了。這個理解我本身也是模模糊糊的,等待之後理解更清楚後補充吧。
對寄存器的數據操做是最快的,依次是一級緩存,二級緩存,三級緩存,內存,硬盤。寄存器的速度和cpu的處理速度是同頻率的,cpu操做起來毫無延遲,而緩存則要高上一個檔次,內存再高上一個檔次,最慢的固然就是硬盤了,若是從硬盤取數據到內存再訪問,那就慢的沒邊了。程序中變量value,i,length變量都是放在棧上操做(有可能某一個是放在寄存器中,沒看彙編代碼,只作推測),那麼速度就會比寄存器慢好多,可是寄存器就那麼十幾個,變量多的時候不能你們都想上,若是將頻繁訪問的數據放入寄存器中操做,很明顯就會在總體上提升處理的速度。最後優化的結果只用了原始的case_one的1/3時間。