初學者疑惑:C語言中,函式反覆呼叫會有什麼問題?
函式開銷困惑
在現代的開發工作中,相信絕大部分的同學手頭的專案都不是從第零行程式碼開始搭建的。各個語言都有自己流行的程式碼框架,如PHP的有Laravel、CodeIgniter、ThinkPHP等等。大家都是在自己的框架的基礎上新增自己的業務程式碼邏輯,開啟開發工作。還記得我們團隊有位開發同學當時問過我一個問題,我們用xx框架這麼重,一個使用者請求過來即使什麼也不幹,都已經進行了那麼多次的函式呼叫了,適合用來做介面開發嗎?
我當時給她的回答是,沒問題放心吧,函式呼叫的開銷很小的,不必擔心。但回答完她的問題之後,我回頭一想,我只知道函式呼叫的開銷很小,但是具體是多大,我心裡並吃不準,這就在我心裡又種下了草。後來終於抽空進行了一次實踐研究,把草拔掉了。
C語言測試
測試程式碼很簡單,這就是一個for迴圈的函式呼叫。程式碼如下:
#include <stdio.h>
int func(int p){
return 1;
}
int main()
{
int i;
for(i=0; i<100000000; i++){
func(2);
}
return 0;
}
函式呼叫耗時測試
我們用 time命令來進行耗時測試
# gcc main.c -o main
# time ./main
real 0m0.335s
user 0m0.334s
sys 0m0.000s
#perf stat ./main
......
1,100,989,673 instructions # 1.37 insns per cycle
......
不過上面的實驗中有個多餘的開銷,那就是for迴圈。我們單獨計算一下這個for的開銷,把func()呼叫那行註釋掉,單獨保留1億次的for迴圈,再重新編譯執行一遍。結果是
time ./main
real 0m0.293s
user 0m0.292s
sys 0m0.000s
perf stat ./main
......
301,252,997 instructions # 0.43 insns per cycle
......
通過上面兩步測試的資料,(0.335-0.293)/100000000=0.4ns。我們可以得出 結論1:每個c函式呼叫耗時大約是0.4ns左右。
函式呼叫CPU指令數分析
我們用 perf命令可以統計到程式執行的底層CPU指令個數。1億次的函式呼叫統計結果如下:
# perf stat ./main
......
1,100,989,673 instructions # 1.37 insns per cycle
......
去掉for迴圈後,單獨1億次的for迴圈統計如下:
# perf stat ./main
......
301,252,997 instructions # 0.43 insns per cycle
......
通過這兩個資料,(1,100,989,673-301,252,997)/100000000=8個。所以我們得出 結論2:每個c函式需要的CPU指令數是8個! 。
函式呼叫CPU指令剖析
如果有同學和我一樣好奇結論2中的每個c函式的CPU指令到底幹了些啥,請和我一起來,否則請開啟3倍速快進。還是上述的實驗程式碼,我們通過gdb的disassemble來檢視一下其內部彙編執行過程,編譯之。
gcc -g main.c -o main
再用gdb命令除錯:
gdb ./main
start
disassemble
mov $0x2,%edi
看到函式到了main函式處,並列印出了main函式的彙編程式碼
......
=> 0x0000000000400486 <+4>: mov $0x2,%edi
0x000000000040048b <+9>: callq 0x400474 <func>
......
這是 進入函式呼叫的兩個CPU指令 ,每個指令大概含義如下:
mov $0x2,%edi
callq
接下來讓我們進入到func函式內部看一下:
break func
run
這時函式停在了func函式的入口處, 繼續使用gdb的disassemble命令檢視彙編指令:
(gdb) disassemble
Dump of assembler code for function func:
0x0000000000400474 <+0>: push %rbp
0x0000000000400475 <+1>: mov %rsp,%rbp
0x0000000000400478 <+4>: mov %edi,-0x4(%rbp)
=> 0x000000000040047b <+7>: mov $0x1,%eax
0x0000000000400480 <+12>: leaveq
0x0000000000400481 <+13>: retq
End of assembler dump.
這6個指令是對應在函式內部執行,以及函式返回的操作。加上前面2個,這樣在結論2中的每個函式8個CPU指令就都水落石出了。
指令3: push %rbpbp暫存器的值壓入呼叫棧,即將main函式棧幀的棧底地址入棧(對應一次壓棧操作,記憶體IO)
指令4: mov %rsp,%rbp被調函式的棧幀棧底地址放入bp暫存器,建立func函式的棧幀(一次暫存器操作)。
指令5: mov %edi,-0x4(%rbp)是從暫存器的地址-4的記憶體中取出,即獲取輸入引數(記憶體IO)
指令6: mov $0x1,%eax對應return 0,即是將返回引數寫到暫存器中(記憶體讀IO)
再接下來的兩個執行令是進行呼叫棧的退棧,以便於返回到main函式繼續執行。是指令3和指令4的逆操作。
指令7: leave q等價於mov %rbp, %rsp,暫存器操作
指令8: retq等價於pop %rbp(記憶體IO)
總結:8次CPU指令中大部分都是暫存器的操作,即使有“記憶體IO”,也是在棧上進行。而棧操作密集,符合區域性性原理,早就被L1快取住了,其實都是L1的IO,所以耗時很低。前面實驗結果表明1次函式呼叫的開銷是0.4ns, 耗時竟然小於1次真正實體記憶體IO的耗時(40ns左右)。
指令並行
不知道大家有沒有人注意到,前面兩次perf stat的結果中分別有如下兩個提示
0.43 insns per cycle
1.37 insns per cycle
這是說現代的CPU可以通過流水線的方式對CPU指令進行並行處理,當指令符合並行規則的時候,每個CPU週期內執行的指令數可能會大於1。這就是 CPU指令並行 的功勞。 所以增加函式呼叫後耗時並沒有增加太多,除了函式呼叫本身開銷不大的原因以外,還有一個原因就是函式呼叫讓CPU的流水線並行技術得以施展,每秒處理的CPU指令數更多了。
PHP語言測試
很多同學又會問題,你用的是C語言進行測試,效能當然高了。
“我用的可是PHP,這可是指令碼語言”
“我用的可是Java,中間可還有一層虛擬機器”
“我用的可是...”
好了,不抬槓,我們繼續試一試不就完了麼。就用php來繼續實驗一把。
<?php
function func(){
return true;
}
for($i=0;$i<10000000;$i++){
func();
}
實驗結果:
php7: 1000W次耗時0.667s,減去0.140s的for迴圈耗時,平均每次函式呼叫耗時52ns
php53:1000W次耗時2.1s,減去0.5s的for迴圈耗時,平均每次耗時160ns
結論
php的函式呼叫確實比c的要慢很多,從不到1ns升高到了50ns左右。因為php又用c虛擬了一層指令集,這層指令集還需要變成CPU的指令集後才可以真正執行。但是要知道的是ns這個時間單位太小了,假如你用的框架特別變態,一個使用者請求來了直接就搞了1000次的函式呼叫,那麼消耗在函式呼叫上的時間會是50ns*1000=50us。這和程式碼框架化後給團隊專案帶來的便利性來對比的話,這點時間開銷,我覺得仍然是可以忽略的。
好了,今天的分享就到這裡了。看到這裡,你是不是對“C語言”又有了一點新的認知呢~如果你喜歡這篇文章的話,動動小指,加個關注哦~
最後,如果你也想成為程式設計師,想要快速掌握程式設計,這裡為你分享一個學習企鵝圈子!
裡面有資深專業軟體開發工程師,線上解答你的所有疑惑~C語言入門“so easy”
程式設計學習書籍:
程式設計學習視訊:
相關文章
- 解決vscode c語言中scanf函式的輸入問題VSCodeC語言函式
- C 語言中的 time 函式函式
- 初學者學Python還是C語言?兩者之間有什麼不同?PythonC語言
- C語言中qsort函式的用法C語言函式
- C語言初學者最常問的幾個問題C語言
- C語言中函式printf()和函式scanf()的用法C語言函式
- C語言中函式的返回值C語言函式
- 淺談C語言中函式的使用C語言函式
- C語言函式呼叫棧C語言函式
- C語言中變參函式傳參探究C語言函式
- "->" 在c語言中是什麼意思?C語言
- 函式呼叫棧的問題函式
- 嵌入式C語言中的組成結構是什麼C語言
- Python語言中=和==有什麼區別?Python
- Python學習教程_Python語言中=和==有什麼區別?Python
- C語言中陣列溢位是什麼C語言陣列
- C 語言中,如果函式宣告瞭返回型別,但執行路徑中沒有 return 語句,則返回什麼資料值呢?函式型別
- C語言中的關鍵字有哪些,分別代表什麼意思C語言
- C語言中四捨五入問題總結C語言
- 什麼是IIFE(立即呼叫函式表示式)?函式
- c語言是如何處理函式呼叫的?C語言函式
- C語言中結構體struct的對齊問題C語言結構體Struct
- 【C語言】函式的概念和函式的呼叫(引數傳遞)C語言函式
- C程式函式呼叫&系統呼叫C程式函式
- c語言初學者氣泡排序C語言排序
- dart系列之:dart語言中的函式Dart函式
- 初學者在學習Python語言時,要注意哪些問題?Python
- C語言有關函式淺析C語言函式
- Python語言中合法變數命名有什麼規則?Python變數
- 為什麼程式設計初學者會選擇 Python 作為入門語言?程式設計Python
- 記錄一次Halcon C++ ReadShapeModel 反覆呼叫時記憶體洩漏問題C++記憶體
- c語言中 *p++ 和 (*p)++ 有什麼區別?以及C語言運算子的優先順序。整理。C語言
- C++語言中 *與&的作用分別是什麼啊?C++
- C語言中抽象函式與具體實現的命名與組織C語言抽象函式
- 函式宣告與函式表示式有什麼區別?函式
- 什麼是C++ setw() 函式?C++函式
- c語言學習筆記===函式C語言筆記函式
- 伺服器過載會有什麼問題伺服器