給函數包一層皮性能會下降嗎?

如果原先的函數是

bool funA(void* arg);

現在又寫了個函數

bool funB(void* arg)

{

return funA(arg);

}

尤其是函數funA本身運算量就很小的情況下,多了一次調用,性能會下降明顯嗎?

編譯器會自動優化嗎?


編譯器優化以後,這種wrap不會帶來額外開銷。

對於這種純粹的wrap,也就是wrap_func簡單的調用原函數時,編譯器會優化處理為一個函數(vs 2012)或者複製一份(gcc 4.8)。不論是哪種方式,對效率都沒有影響。比如如下代碼:

#include &

int add(int x, int y)
{
return x + y;
}

int wrap_add(int x, int y)
{
return add(x, y);
}

int main(int argn, char *argv[])
{
printf("add = %p
", add);
printf("wrap_add = %p
", wrap_add);
printf("r = %d
", wrap_add(1, 2));
return 0;
}

如果是vs,我們會看到add和wrap_add是同一個地址(注意使用release模式編譯);

如果是gcc,我們可以看到wrap_add函數如下(注意打開O2)

(gdb) disassemble wrap_add
Dump of assembler code for function wrap_add(int, int):
0x08048490 &<+0&>: mov 0x8(%esp),%eax
0x08048494 &<+4&>: add 0x4(%esp),%eax
0x08048498 &<+8&>: ret

如果考慮更複雜的情況,比如wrap_add在調用add做了一些簡單的處理,如使用printf進行輸出,編譯器會有幾種優化方式:

1. 直接將額外操作(如printf)放到調用處(如main),然後再直接調用add

2. 直接複製一份add的代碼在wrap_add調用add的地方

3. 處理額外操作(如printf)以後直接通過jmp跳轉到add處

不論是哪種,對效率都不會有什麼影響。

比如如下代碼:

int add(int x, int y)
{
return x + y;
}

int wrap_add(int x, int y)
{
printf("Wrap!
");
return add(x, y);
}

我們看到彙編代碼如下:

(gdb) disassemble wrap_add
Dump of assembler code for function wrap_add(int, int):
0x080484c0 &<+0&>: sub $0x1c,%esp
0x080484c3 &<+3&>: movl $0x8048570,(%esp)
0x080484ca &<+10&>: call 0x8048330 &


0x080484cf &<+15&>: mov 0x20(%esp),%eax // Add的實現
0x080484d3 &<+19&>: add 0x24(%esp),%eax // Add的實現
0x080484d7 &<+23&>: add $0x1c,%esp
0x080484da &<+26&>: ret

如果add很長,複製不划算則會轉為jmp:

int add(int x, int y)
{
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; }
if ((x 1) == 0) { y += x &>&> 1; x &>&>= 1; } // 讓函數變長
return x + y;
}

int wrap_add(int x, int y)
{
printf("Wrap!
");
return add(x, y);
}

則彙編代碼如下:

(gdb) disassemble wrap_add
Dump of assembler code for function wrap_add(int, int):
0x08048510 &<+0&>: push %esi
0x08048511 &<+1&>: push %ebx
0x08048512 &<+2&>: sub $0x14,%esp
0x08048515 &<+5&>: mov 0x20(%esp),%ebx
0x08048519 &<+9&>: mov 0x24(%esp),%esi
0x0804851d &<+13&>: movl $0x80485d0,(%esp)
0x08048524 &<+20&>: call 0x8048330 &


0x08048529 &<+25&>: mov %ebx,0x20(%esp)
0x0804852d &<+29&>: mov %esi,0x24(%esp)
0x08048531 &<+33&>: add $0x14,%esp
0x08048534 &<+36&>: pop %ebx
0x08048535 &<+37&>: pop %esi
0x08048536 &<+38&>: jmp 0x80484b0 & // 跳轉到add


其他答主給出了編譯器內聯時的說明。如果只是這一個函數,在如此trivial的情況下,內聯函數之後,性能幾乎不會下降。但如果只看題主提問的title,那麼回答是:給函數包一層皮會使性能下降。

如果沒有必要,就不要做trivial的包裝。當你引入wrap,又不知道性能會不會下降時,最合理的假設是會下降,然後進行謹慎的profiling。首先,inline是編譯器的行為,不完全受程序員控制,也不存在「一定會inline」的函數,在很多情況下函數不會被內聯。其次,inline並不是免費午餐,付出的代價可能是程序變大,當inline函數比較大,或wrap層數比較多的時候,inline對性能的影響很難判斷,交給編譯器自主決定是最好的選擇。

很多情況下對函數進行包裝是非常合理並且有益處的,比如改變函數的介面,或僅僅是在一個特定的應用場合改變一個名字,使代碼可讀性更好。但是如果一開始就假設這個舉動是完全free的,那麼可能會產生濫用,比如將注釋寫入函數名中,或傾向於將函數實現全部放入頭文件以促使inline。如果你在做大型的項目,並與其他人合作,這樣做是得不償失的。

最後,還是提醒題主在自己的生產環境(編譯器、編譯參數)下做個測試,並引入比你問題中更複雜一些的例子(如果是題主問題中的例子,在較新版本gcc的情況下,以-O2編譯,是很可能內聯的),自己測試一下性能是否會有明顯的下降。假設所有公司都用較新的gcc,或合理的編譯參數,是不現實的。


給函數包皮性功能會下降嗎?

這麼說吧,包皮了之後的函數敏感度會下降,所以射出返回值的時間也會延長,從這點上來說應該性功能應該是提升了。


會下降。假設程序編譯成x86機器指令。

函數調用的結構是

push arg
call fun
add esp,4 ; C調用

一共三條指令。

如果fun只判斷arg是否為空,結構是

mov eax,[arg]
test eax,eax
sete al
retn

一共四條指令。

如果假設所有指令執行的時間相同,則一層調用會增加75%的執行時間,可見性能下降還是很明顯的。

編譯器對這種情況的優化叫做內聯(inline),實際做的事情就是把函數的代碼或指令直接複製到調用的位置,而不再通過函數調用,以節約調用的開支。

在你這個例子中funB是典型的小函數,優化結果是被funA直接替換。如果funA也很小,很有可能連funA也不存在了,其代碼直接插入到原來調用funB的位置。


會。

就算看錯了題目,還是會。

難道只有我看錯了題目嗎。


只有我一個人看成了函數有包皮性慾要下降么?


工程狗表示。。。-O2日一切。。。


應該沒什麼影響


尾調用,同尾遞歸,理論上可以優化成不壓棧的。


不會,以gcc為例

https://gcc.gnu.org/gcc-4.7/changes.html

Interprocedural optimization improvements:

  • Heuristics now take into account that after inlining code will be optimized out because of known values (or properties) of function parameters. For example:

    void foo(int a)
    {
    if (a &> 10)
    ... huge code ...
    }
    void bar (void)
    {
    foo (0);
    }


編譯器優化就是讓你怎麼爽怎麼寫的,只要你寫的不是太過分。

僅題主這種方式,開了編譯優化性能不會下降。

編譯性能倒是有可能下降。


刷知乎的時候往下拉,看到包皮,往回拉,卻沒找到。仔細一看。。。


推薦閱讀:

自己寫的COFF文件格式和編譯器生成的.obj文件一樣嗎?
寫編譯器需要把彙編語言學到什麼程度就夠用了?入門到進階有什麼好書值得讀?
「Monokai」這個詞是什麼意思?
如何評價Tinyfool在swiftcon大會上發言跑題被罵一事?
為什麼好多編程「牛人」不喜歡用Microsoft Visual Studio?

TAG:編程 | 信息技術IT | 編譯器 |