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

Coo*_*kie 10 c++ performance code-organization

比如说,我的头文件是:

class A
{
    void Complicated();
}
Run Code Online (Sandbox Code Playgroud)

和我的源文件

void A::Complicated()
{
    ...really long function...
}
Run Code Online (Sandbox Code Playgroud)

我可以将源文件拆分为

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(...)
}
Run Code Online (Sandbox Code Playgroud)

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

Mat*_* M. 11

您应该标记这些函数,static以便编译器知道它们是该转换单元的本地函数.

如果没有static编译器不能假设(禁止LTO/WPA)该函数只被调用一次,那么内联它的可能性就会降低.

使用LLVM试用页面进行演示.

这就是说,对于可读性第一码,微优化(并且这样的调整一个微型优化)应该只跟从性能的措施.


例:

#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);
  }
}
Run Code Online (Sandbox Code Playgroud)

生产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
Run Code Online (Sandbox Code Playgroud)

没有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
}
Run Code Online (Sandbox Code Playgroud)

  • 根据"原则",我认为你应该将它们放在一个未命名的命名空间中. (3认同)

Bli*_*ndy 7

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

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

  • @Blindy:如果这段代码构成每秒迭代数百万次的内循环的一部分,那么是的,它确实*重要。 (2认同)

Mar*_*som 7

一个复杂的功能可能会以其功能内的操作为主导; 即使没有内联,函数调用的开销也不会引人注意.

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

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

  • @phkahler:inline关键字不会强制编译器内联函数. (2认同)