函數傳遞引用 與 直接操縱全局變數 消耗資源的區別?

成千上萬次的循環中


  • "性能"

  • "效率"

  • "優化"

初學者尤其不應該關心這三大毒藥

我想樓主問的是下面這段代碼中param和global的區別 (附在答案最後)

就在這樣的例子中, 它們完全沒區別......

調用param和調用global的區別如下

param:

  1. 把1放入寄存器%wtf (什麼?壓棧?早就不這麼玩了...)

  2. 調用param
  3. 從寄存器%wtf取值
  4. do wtf you want

global:

  1. 調用global
  2. 從內存_g中取值存入寄存器%wtf
  3. 從寄存器%wtf取值
  4. do wtf you want

在這個例子里, param函數比global函數效率還要高上那麼一丁點呢...哈哈哈哈

我這麼無聊來寫這麼無聊的答案, 就是希望更多初學者能明白, 性能/效率/優化都是個屁啊, 初學者不要想這些沒用的東西, 誤人誤己

PS: 樓上說得不對, 錯太多就不吐槽了

update:

回應一下@羅佳希 的答案

你錯了

即便是傳一個變數, 那一步也只是變得和全局變數的情況一樣, 把變數的值(在內存中, 和global函數一樣)複製到寄存器, 但從彙編層面上來看, 他們之間仍然沒有差異

參數入棧只是你看的書上這麼說而已(順便問下哪本書...), 你實踐一下, 去看看彙編代碼, 現在沒有任何一個編譯器會編譯出參數入棧這種爛代碼來的

當然這時候你又會說了, 哎呀你不到10個寄存器, 我弄你100個參數, 搞死你, 這下總要入棧了吧?

嗯, 是不得不入了, 可是即便拋開這種小概率的無聊假設, 還有cache和分支預測呢...當然這就不是彙編代碼層面上的東西就不算好了

不過這個問題很無聊沒什麼意義, 搞得我們的爭辯也沒有意義了...囧

回應一下@Kingsam Chen

請問在你做過的項目中, 是否有遇到過需要性能優化的情況?

請問你是怎麼優化的?

不過從下面這句我就知道你要麼沒有做過什麼正經項目, 要麼沒有搞過優化...

當然最大的可能是兩者都沒有做過...囧

最後對於程序效率,一般而言,影響的無非只有兩點: 1)編譯器優化 2)採用的演算法。 與其在這些細枝末節上糾纏,不如相信你的編譯器

int g = 1;

int param(int g);
int global(void);

int
main(void) {
int i;
for(i = 0; i &< 10000000000; i++) { param(1); // global(); } return 0; } int param(int g) { int a = g + 1; return a; } int global(void) { int a = g + 1; return a; }


才一萬次循環你真的以為會有很大的差別嗎?

我之前寫的一個dfs,單線程每秒要做百萬次判斷,相比之下一萬次循環,傳入的參數早就優化到寄存器里了。拋去優化,引用也是指針,全局數組也是指針,往後的數據存取同樣是基址+偏移,看不出什麼差別。

真要比較效率,必須得做profile,而數據分布對性能的影響可能更值得注意。大多數情況下,不停循環的熱點數據常留在緩存里,所謂的「遠近」可能影響不大。但是鑒於N路相關的cache,距離遠了還是有增加cache miss的幾率的。

平常寫代碼就別糾結到這個層面了(反正編譯器行為你也控制不了)。多注意避免不必要的深拷貝就行了(最近看到的代碼,運算1s,new和delete卻花了100s)


函數傳遞引用其實就是往stack里壓入一個4-byte的對象地址,調用完畢後還要恢復用掉的棧空間。

全局變數是直接存儲在全局/靜態區的,運行時一直有效。

在一個循環次數很大的循環里調用函數可能會帶來一些性能問題,至少有人士這麼認為的,所以才會有inline的存在。

--------

下面@曲維姜 提到的有幾點觀點我是不太贊成的,理由下面也說了。關於性能優化這個點,有必要提下。

他說的『初學者不應該關心性能效率優化』的論調我其實也是不同意的。

我贊成的是『初學者不應該過分關心性能效率優化』。

期間的差別很明顯,初學者應當注意一些最基本的,具有良好性能的編碼常識(上面說的是C,C++嚇死人的那套我就不提了),例如:結構體一般情況下採用指針傳參,選擇性價比好的演算法 .etc

最基本的注意點沒幾個,經過短期的編碼練習就能很快掌握,而且也不是什麼問題。

要小心的是過分關心性能優化,就像題主這樣的行為。一般情況下,程序的性能到還沒有需要非常關心的地步。相比下,良好的設計和編碼實現才是重心。

除非跑出來的profiling結果證實確定需要優化,再去優化瓶頸點,否則,都是過早優化。高老爺爺就曾經說過:過早優化是萬惡之源

最後對於程序效率,一般而言,影響的無非只有兩點: 1)編譯器優化 2)採用的演算法。 與其在這些細枝末節上糾纏,不如相信你的編譯器


先簡單說下區別,「傳遞引用」 這種方式應該都是往線程棧push引用的數值(現代化的存寄存器這個不了解),在編譯器的角度,不管是引用還是非引用,push進去的都是一個數值,如果傳遞的是引用參數,那麼就是引用的數值,都會佔用線程棧的內存空間,在不斷循環調用過程中,棧會不停增長,而使用全局變數的方式,編譯型語言通過編譯器,直接就將其填充為取全局變數地址的值,不需要額外的存儲空間,在不停的循環過程,依然如此。

另外,不是很同意盡量少用全局變數這樣的說法,在內存稀缺設備上,全局變數的使用是很好優化資源戰佔用的情況,引用傳遞的方式,也很容易造成資源浪費甚至內存泄漏。

所以,全局變數或者引用傳遞都需要考慮使用場景,不能一概而論。

下圖從某度扒來一張圖,線程棧區空間也是有限,不停循環過程中還是有可能分配完棧區空間,全局變數一般都在數據區或者堆區。


不太同意 @曲維姜 的答案

示例中的param函數操作的是直接數,而不是引用傳遞。(我是這麼認為的),因為操作的是直接數,所以compiler會直接把1放入寄存器沒有錯,但事實上的引用傳遞會導致間接定址。

引用傳遞(pass-by-reference)過程中,被調函數的形式參數雖然也作為局部變數在堆棧中開闢了內存空間,但是這時存放的是由主調函數放進來的實參變數的地址。被調函數對形參的任何操作都被處理成間接定址,即通過堆棧中存放的地址訪問主調函數中的實參變數。正因為如此,被調函數對形參做的任何操作都影響了主調函數中的實參變數。

循環中,調用函數時會入棧引用變數的地址,如果是全局變數則應該不用有此操作。

不過很同意,如非必須(學術上),實際項目時不應該過度關注性能,效率,優化。


推薦閱讀:

C語言里64位程序long類型的變數 長度是4還是8?
C++ 完全兼容 C 語言嗎?
C Primer Plus第五版的練習題答案能找到官方正版的嗎?
為什麼0取反再右移任意位得出數總是-1?
linux下面有哪些純c的項目值得一讀源代碼?最近對c語言比較感興趣,但是c++又不太深入?

TAG:編程語言 | 編程 | C編程語言 | 數據結構 |