现代C ++编译器内联函数是否只调用一次?

时间:2011-08-17 16:41:03

标签: c++ performance code-organization

如我所说,我的头文件是:

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(...)
}

完全为了可读性而不担心性能方面的影响?

5 个答案:

答案 0 :(得分:11)

您应该标记函数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
}

答案 1 :(得分:7)

取决于别名(指向该函数的指针)和函数长度(分支中内联的大函数可能会将另一个分支从缓存中抛出,从而影响性能)。

让编译器担心,你担心你的代码:)

答案 2 :(得分:7)

一个复杂的函数可能会以函数内的操作为主;即使它没有内联,函数调用的开销也不会很明显。

你无法控制函数的内联,最好的方法就是尝试并找出函数。

对于较短的代码片段,编译器的优化器可能更有效,因此即使它没有内联,您也可能会发现它变得更快。

答案 3 :(得分:0)

如果将代码拆分为逻辑分组,编译器将执行它认为最好的方法:如果它简单易行,编译器应该内联它并且结果是相同的。但是,如果代码很复杂,那么进行额外的函数调用实际上可能更快而不是执行所有内联工作,因此您也可以选择执行此操作。最重要的是,逻辑上分割的代码对于维护者来说更容易理解并避免将来的错误。

答案 4 :(得分:0)

我建议你创建一个帮助类来将复杂的函数分解为方法调用,就像你提出的那样,但没有漫长,乏味和难以理解的任务,即将参数传递给每个这些较小的函数。通过使这些参数成为辅助类的成员变量,只传递一次这些参数。

此时不要专注于优化,确保您的代码可读,99%的时间都可以。