函數調用:即調用函數調用被調用函數,調用函數壓棧,被調用函數執行,調用函數出棧,調用函數繼續執行的一個看似簡單的過程,系統底層卻做了大量操作。
操作:
1, 調用函數幀指針(函數參數,局部變量,棧幀狀態值,函數返回地址)入棧,棧指針自減
2, 保存調用函數的狀態數據入寄存器
3, 被調用函數幀指針入棧,執行當前的被調用函數
4, 被調用函數執行結束,退棧,返回到調用函數的幀指針,從寄存器中恢復當時狀態數據
5, 繼續執行調用函數,直至結束
即整個調用操作有一個壓棧出棧,保存和恢復狀態數據的過程。而系統棧內存是有默認的固有大小。有多少次函數調用就會分配多少棧幀。故,函數調用性能影響有如下因素:
1,函數遞歸層數;
2,參數個數(參數簽名所占內存大小)
2.1同類型不同參數個數;
2.2同參數個數不同參數類型;
2.3同參數類型同參數個數,但參數類型所占內存大小不同;
3,函數棧大小,即函數局部變量所占棧大小。
為了測試C語言函數調用性能(時間消耗)因素,編寫了一個簡單程序運行在如下環境中:
Intel(R) Core(TM) i5-2400 CPU @ 3.10GHz memery size:7833700 kB(7.47GB)
在函數調用的開始與結束處,用time.h中的clock()函數返回CPU時鍾計時單位數(下表中的starttime和endtime),用durationtime=endtime-starttime表示函數調用的時間消耗。如下:
clock_t starttime=clock();
函數調用…
clock_t endtime=clock();
//除以CLOCKS_PER_SEC,得到以秒為單位的時間結果
double durationtime=(double)(endtime-starttime)/CLOCKS_PER_SEC;//表示函數調用占用cpu的時間,不包括子進程或者printf等的操作的時間
注:clock()記錄的是進程占用cpu的時間,精確度為微秒;詳細講解clock()函數的網址:http://site.douban.com/199048/widget/notes/12005386/note/253542964/
一.函數遞歸層數(循環1000000次)
棧(字節) |
參數(字節) |
遞歸次數 |
總函數調用時間消耗(秒) |
每循環函數調用時間消耗(微秒) |
每次函數調用平均時間消耗(納秒) |
1024 |
24 |
10 |
2.9 |
2.9 |
290 |
1024 |
24 |
20 |
5.713 |
5.713 |
285.65 |
1024 |
24 |
30 |
9.025 |
9.025 |
300.83 |
1024 |
24 |
50 |
16.0767 |
16.0767 |
321.534 |
1024 |
24 |
80 |
21.79 |
21.79 |
272.375 |
1024 |
24 |
100 |
30.73 |
30.73 |
307.3 |
1024 |
24 |
200 |
66.24 |
66.24 |
331.2 |
注:平均每次函數調用時間消耗=durationtime/調用層數/ 循環次數
每循環函數調用時間消耗=durationtime/ 循環次數
函數調用根據不同的調用層數不同的時間平均消耗,如下折線圖:
圖1
每次函數調用平均時間消耗,如下折線圖:
圖2
結論:1,在參數所占內存相同和函數棧大小相同的情況下,函數調用的時間消耗隨着函數調用層數增加而增加;如圖1;
2,在參數所占內存相同和函數棧大小相同的情況下,每次函數調用的時間消耗大概在300納秒左右;如圖2;
二,函數棧大小
循環次數 |
棧(字節) |
參數 (字節) |
遞歸次數 |
總函數調用時間消耗(秒) |
每循環函數調用時間消耗(微秒) |
平均每次函數調用(納秒) |
1000000 |
16 |
24 |
50 |
9.4 |
9.4 |
184 |
1000000 |
32 |
24 |
50 |
9.37 |
9.37 |
187.4 |
1000000 |
64 |
24 |
50 |
9.5 |
9.5 |
190 |
1000000 |
128 |
24 |
50 |
10.415 |
10.415 |
208.3 |
1000000 |
256 |
24 |
50 |
11.805 |
11.805 |
236.1 |
1000000 |
512 |
24 |
50 |
14 |
14 |
280 |
1000000 |
1024 |
24 |
50 |
16.0767 |
16.0767 |
321.534 |
1000000 |
2048 |
24 |
50 |
18.42 |
18.42 |
368.4 |
注:平均每次函數調用時間消耗=durationtime/調用層數/ 循環次數
每循環函數調用時間消耗=durationtime/ 循環次數
函數調用根據不同的調用層數不同的時間平均消耗,如下折線圖:
圖3
每次函數調用平均時間消耗,如下折線圖:
圖4
結論:1,在函數參數相同和函數調用層數相同的情況下,函數調用時間消耗隨函數棧大小的增加而增加;如圖3;
2,在函數參數相同和函數調用層數相同的情況下,每次函數調用時間消耗隨函數棧大小的增加而增加;如圖4;
三,參數個數
棧(字節) |
參數 (字節) |
遞歸次數 |
總函數調用時間消耗(秒) |
每循環函數調用時間消耗(微秒) |
平均每次函數調用(納秒) |
1024 |
24 |
50 |
16.0767 |
16.0767 |
321.5 |
1024 |
36 |
50 |
16.245 |
16.245 |
324.9 |
1024 |
48 |
50 |
16.345 |
16.345 |
326.9 |
1024 |
60 |
50 |
15.915 |
15.915 |
318.3 |
1024 |
72 |
50 |
14.29 |
14.29 |
285.8 |
1024 |
84 |
50 |
15.76 |
15.76 |
315.2 |
1024 |
96 |
50 |
15.14 |
15.14 |
302.8 |
1024 |
108 |
50 |
13.975 |
13.975 |
279.5 |
1024 |
120 |
50 |
16.68 |
16.68 |
333.6 |
1024 |
144 |
50 |
15.37 |
15.37 |
307.4 |
1024 |
180 |
50 |
14.42 |
14.42 |
288.4 |
1024 |
192 |
50 |
14.62 |
14.62 |
292.4 |
注:平均每次函數調用時間消耗=durationtime/調用層數/ 循環次數
每循環函數調用時間消耗=durationtime/ 循環次數
函數調用根據不同的函數參數大小的時間平均消耗,如下折線圖:
每次函數調用平均時間消耗,如下折線圖:
結論: 經過前幾次的函數測試,雖然存在誤差,但是仍然可以得出參數對於函數調用的時間消耗的影響,在於參數所占內存大小;函數傳參存在兩種方式:值傳參和引用傳參;兩種方式在一般情況下,不會占用過多的內存;故,在一般情況下,參數對函數調用的時間消耗的影響不明顯;
四,結論:
1,在函數參數大小為24字節和函數棧大小為1024字節的情況下,遞歸50次的函數時間消耗為16.0767微秒,可以粗略得出每次函數調用(壓棧出棧)的時間消耗為320納秒左右;
思路:1,函數參數大小:函數參數分為值傳參和引用傳參(參數的指針);一般值傳參為常用的值類型,這樣的參數一般不會占用過多的內存;引用參數是參數地址也不會占用過多內存;所以在一般情況下,函數參數對函數調用時間消耗影響不大;
2,計數:循環1000000次函數遞歸,是為了想提高數據的精確性和便於計算;1秒=1000000微秒;
3,遞歸層數:選擇可能常規下遞歸的層數(24--35)
4,函數棧大小:按照以太網的最大字節1500字節,選擇在1024字節左右做以上實驗;
代碼:
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #include<time.h> 5 #define array_len 256 6 typedef struct { 7 int typeone; 8 int typetwo; 9 }struct_type; 10 long call_back(int call_num,int typeone,int typetwo,int typethree,long p_recorde) 11 { 12 if(call_num<=0)return p_recorde; 13 int i_rand[array_len]; 14 int i=0; 15 clock_t start_time,end_time; 16 start_time=clock(); 17 for(i=0;i<array_len;i++) 18 { 19 i_rand[i]=rand(); 20 } 21 end_time=clock(); 22 p_recorde+=(long)(end_time-start_time); 23 call_back(call_num-1,typeone,typetwo,typethree,p_recorde); 24 } 25 void main(int argc,char *argv[]) 26 { 27 int loop_num=atoi(argv[1]),call_num=atoi(argv[2]); 28 long p_recorde=0,sum=0; 29 clock_t start_time,end_time; 30 start_time=clock(); 31 int i; 32 for(i=0;i<loop_num;i++) 33 { 34 sum_loop+=call_back(call_num,0,0,0,p_recorde); 35 } 36 end_time=clock(); 37 double duration_time=(double)(end_time-start_time)/CLOCKS_PER_SEC-(double)sum_loop/CLOCKS_PER_SEC; 38 printf("sum=%f duration=%f\n",sum_loop,duration_time); 39 }
代碼思路:1,為了減少數據cache命中的影響,在每次函數調用中用了rand()獲取隨機數,並記錄時間消耗a;
2,記錄函數調用的時間總消耗b,b-a的差即為函數調用的時間總消耗;