2011-08-17 33 views
9

如,說我的頭文件是:現在的C++編譯器內聯函數只能調用一次嗎?

class A 
{ 
    void Complicated(); 
} 

我的源文件

void A::Complicated() 
{ 
    ...really long function... 
} 

我可以將源文件拆分成

void DoInitialStuff(pass necessary vars by ref or value) 
{ 
    ... 
} 
void HandleCaseA(pass necessary vars by ref or value) 
{ 
    ... 
} 
void HandleCaseB(pass necessary vars by ref or value) 
{ 
    ... 
} 
void FinishUp(pass necessary vars by ref or value) 
{ 
    ... 
} 
void A::Complicated() 
{ 
    ... 
    DoInitialStuff(...); 
    switch ... 
     HandleCaseA(...) 
     HandleCaseB(...) 
    ... 
    FinishUp(...) 
} 

完全是爲了可讀性和沒有任何恐懼對性能的影響?

+1

也許,也許不是。編譯器程序員可能是你最好的選擇,根據你使用的編譯器來決定。 – DumbCoder

+0

我想知道內聯點會是什麼......電話很便宜。 – 2011-08-17 16:45:18

+2

這些都不是一個循環?您希望從避免幾次函數調用的開銷中獲得多少時間?一納秒? – UncleBens

回答

10

您應該標記函數static,以便編譯器知道它們對於該翻譯單元是本地的。

沒有static編譯器不能假設(禁止LTO/WPA)該函數只被調用一次,因此不太可能將其內聯。

演示使用LLVM Try Out頁面。

這就是說,代碼的可讀性首先,微優化(並且這樣調整微優化)應該僅在性能測量之後出現。


例子:

#include <cstdio> 

static void foo(int i) { 
    int m = i % 3; 
    printf("%d %d", i, m); 
} 

int main(int argc, char* argv[]) { 
    for (int i = 0; i != argc; ++i) { 
    foo(i); 
    } 
} 

產生一個與static

; ModuleID = '/tmp/webcompile/_27689_0.bc' 
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" 
target triple = "x86_64-unknown-linux-gnu" 

@.str = private constant [6 x i8] c"%d %d\00"  ; <[6 x i8]*> [#uses=1] 

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind { 
entry: 
    %cmp4 = icmp eq i32 %argc, 0     ; <i1> [#uses=1] 
    br i1 %cmp4, label %for.end, label %for.body 

for.body:           ; preds = %for.body, %entry 
    %0 = phi i32 [ %inc, %for.body ], [ 0, %entry ] ; <i32> [#uses=3] 
    %rem.i = srem i32 %0, 3       ; <i32> [#uses=1] 
    %call.i = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([6 x i8]* @.str, i64 0, i64 0), i32 %0, i32 %rem.i) nounwind ; <i32> [#uses=0] 
    %inc = add nsw i32 %0, 1      ; <i32> [#uses=2] 
    %exitcond = icmp eq i32 %inc, %argc    ; <i1> [#uses=1] 
    br i1 %exitcond, label %for.end, label %for.body 

for.end:           ; preds = %for.body, %entry 
    ret i32 0 
} 

declare i32 @printf(i8* nocapture, ...) nounwind 

沒有static

; ModuleID = '/tmp/webcompile/_27859_0.bc' 
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" 
target triple = "x86_64-unknown-linux-gnu" 

@.str = private constant [6 x i8] c"%d %d\00"  ; <[6 x i8]*> [#uses=1] 

define void @foo(int)(i32 %i) nounwind { 
entry: 
    %rem = srem i32 %i, 3       ; <i32> [#uses=1] 
    %call = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([6 x i8]* @.str, i64 0, i64 0), i32 %i, i32 %rem) ; <i32> [#uses=0] 
    ret void 
} 

declare i32 @printf(i8* nocapture, ...) nounwind 

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind { 
entry: 
    %cmp4 = icmp eq i32 %argc, 0     ; <i1> [#uses=1] 
    br i1 %cmp4, label %for.end, label %for.body 

for.body:           ; preds = %for.body, %entry 
    %0 = phi i32 [ %inc, %for.body ], [ 0, %entry ] ; <i32> [#uses=3] 
    %rem.i = srem i32 %0, 3       ; <i32> [#uses=1] 
    %call.i = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([6 x i8]* @.str, i64 0, i64 0), i32 %0, i32 %rem.i) nounwind ; <i32> [#uses=0] 
    %inc = add nsw i32 %0, 1      ; <i32> [#uses=2] 
    %exitcond = icmp eq i32 %inc, %argc    ; <i1> [#uses=1] 
    br i1 %exitcond, label %for.end, label %for.body 

for.end:           ; preds = %for.body, %entry 
    ret i32 0 
} 
+3

根據「原則」,我認爲你應該把它們放在一個未命名的命名空間中。 – GManNickG

+0

Clang在你的例子中將它嵌入到靜態和非靜態版本中......我認爲它是否在調用內聯中存在顯着差異。這個函數的代碼是否會被髮射是有區別的。 –

+1

@GMan:我贊成'靜態',因爲我覺得它對於人類來說更具可讀性,一個未命名的名字空間需要人記住他在一箇中,並且它並不明顯。 'static'不需要這個「上下文」。 –

7

取決於別名(指向該函數的指針)和函數長度(分支內嵌的大函數可能會將另一分支從緩存中移出,從而傷害性能)。

讓有關編譯器擔心,你擔心你的代碼:)

+1

編譯器能否將更長的函數本身分解爲更小的函數? – Cookie

+0

OP正在詢問如何編寫上述代碼和手動編寫所有內聯代碼之間的比較。所以我想他/她想要一個解釋是否打破一個功能會有不利影響的答案。 –

+0

這聽起來有點不太可能,會有什麼好處?我認爲你更可能發現編譯器一個接一個地複製你的塊:) – Blindy

6

一個複雜的功能可能有它的速度在函數內部操作爲主;即使沒有內聯,函數調用的開銷也不會顯着。

對函數的內聯沒有太多的控制,知道的最好方法就是嘗試一下並找出答案。

編譯器的優化器可能對代碼較短的代碼更有效,所以即使它沒有內聯,也可能會發現它變得更快。

+0

那麼簡單的功能呢? –

+0

@Oli,無論如何,簡單的函數更有可能被自動內聯。 –

+0

沒有太多的控制權?這就是爲什麼有一個「內聯」關鍵字...除非聲明爲靜態,否則不能內聯簡單函數。或者更具體地說,外線呼叫者可以使用非內聯版本。另外,如果一個大功能被分成15個小功能(就像OP所要做的那樣),那將是通話開銷的15倍。 – phkahler

0

如果將代碼拆分爲邏輯分組,編譯器將執行它認爲最好的操作:如果代碼簡短,編譯器應該將其內聯並且結果相同。但是,如果代碼複雜,那麼創建額外的函數調用實際上可能比執行所有內聯工作更快,因此您可以讓編譯器選擇這樣做。最重要的是,對於維護人員來說,邏輯上的分離代碼可以更容易地避免將來的錯誤。

+0

如果該函數只被調用一次,我不確定我們會通過不內聯代碼來看到加速... *除非*您實際上想要跳過函數調用並使函數內聯,因此垃圾指令緩存。它是最新的嗎? –

+0

我的想法是,通過將代碼分解爲邏輯功能塊,編譯器可以更智能地利用寄存器,以及其他我無法想象的可能性。 –

0

我建議你創建一個輔助類來打破你的complica將函數轉換爲方法調用,就像您提出的那樣,但沒有將參數傳遞給每個這些小函數的漫長,無聊和不可讀的任務。通過使它們成爲輔助類的成員變量,只傳遞一次這些參數。

,不要着眼於優化在這一點上,確保您的代碼是可讀的,你會的時間細99%。

相關問題