"直列"关键字vs"内联"概念

时间:2014-11-20 15:11:39

标签: c++ c language-lawyer inline-functions

我问这个基本问题是为了让记录笔直。提到了this questionits currently accepted answer,但这并不令人信服。然而,second most voted answer提供了更好的洞察力,但也不是完美的。

在阅读下文时,请区分inline 关键字和“内联”概念

这是我的看法:

内联概念

这样做是为了节省函数的调用开销。它更类似于宏式代码替换。没有什么可争议的。

inline关键字

感知A

  

inline关键字是编译器的请求,通常用于较小的函数,因此编译器可以对其进行优化并进行更快速的调用。编译器可以忽略它。

我对此提出异议,原因如下:

  1. 没有内联更大和递归的函数,编译器会忽略inline关键字。
  2. 优化程序会自动内联较小的函数,无论是否提及inline关键字。
  3. 很明显,用户使用关键字inline无法控制功能内联。

    感知B

      

    inline 没有与内联的概念有关。将inline置于大/递归函数之前将无济于事,较小的函数将不需要它,因为内联。

         

    确定性使用inline是为了维护 One   定义规则

    即。如果一个函数是用inline声明的,那么只有 下面的东西是强制性的:

    1. 即使在多个翻译单元中找到它的主体(例如,在多个.cpp文件中包含该标头),编译器也只生成1个定义并避免多个符号链接器错误。 (注意:如果该函数的主体不同,则它是未定义的行为。)
    2. inline函数的主体必须在使用它的所有翻译单元中可见/可访问。换句话说,在inline中声明.h函数并在任何一个 .cpp文件中定义将导致其他{{未定义的符号链接器错误“ 1}}文件
    3. 判决

      “A”感觉完全错误而“B”感知完全正确

      标准中有一些引用,但我期待一个答案,从逻辑上解释这个判决是真还是假。

2 个答案:

答案 0 :(得分:55)

我不确定你的说法:

  

无论是否提及内联,优化器都会自动“内联”较小的函数...   很明显,用户使用关键字inline无法控制“内联”功能。

我听说编译器可以自由地忽略你的inline请求,但我认为他们没有完全忽视它。

我查看了Github存储库中的Clang和LLVM以找出答案。 (谢谢,开源软件!)我发现 inline关键字确实使Clang / LLVM更有可能内联函数。

搜索

the Clang repository中搜索单词inline会导致令牌说明符kw_inline。看起来Clang使用一个聪明的基于宏的系统来构建词法分析器和其他与关键字相关的函数,因此可以找到像if (tokenString == "inline") return kw_inline这样的直接函数。但Here in ParseDecl.cpp,我们发现kw_inline会调用DeclSpec::setFunctionSpecInline()

case tok::kw_inline:
  isInvalid = DS.setFunctionSpecInline(Loc, PrevSpec, DiagID);
  break;

Inside that function,如果它是重复的inline,我们设置一下并发出警告:

if (FS_inline_specified) {
  DiagID = diag::warn_duplicate_declspec;
  PrevSpec = "inline";
  return true;
}
FS_inline_specified = true;
FS_inlineLoc = Loc;
return false;

在其他地方搜索FS_inline_specified,我们在位域中看到它是一位,it's used in a getter functionisInlineSpecified()

bool isInlineSpecified() const {
  return FS_inline_specified | FS_forceinline_specified;
}

搜索isInlineSpecified()的调用网站,我们找到the codegen,我们将C ++解析树转换为LLVM中间表示:

if (!CGM.getCodeGenOpts().NoInline) {
  for (auto RI : FD->redecls())
    if (RI->isInlineSpecified()) {
      Fn->addFnAttr(llvm::Attribute::InlineHint);
      break;
    }
} else if (!FD->hasAttr<AlwaysInlineAttr>())
  Fn->addFnAttr(llvm::Attribute::NoInline);

Clang to LLVM

我们完成了C ++解析阶段。现在,我们的inline说明符将转换为与语言无关的LLVM Function对象的属性。我们从Clang切换到the LLVM repository

正在搜索llvm::Attribute::InlineHint yields the method Inliner::getInlineThreshold(CallSite CS) (带有可怕的无括号if块)

// Listen to the inlinehint attribute when it would increase the threshold
// and the caller does not need to minimize its size.
Function *Callee = CS.getCalledFunction();
bool InlineHint = Callee && !Callee->isDeclaration() &&
  Callee->getAttributes().hasAttribute(AttributeSet::FunctionIndex,
                                       Attribute::InlineHint);
if (InlineHint && HintThreshold > thres
    && !Caller->getAttributes().hasAttribute(AttributeSet::FunctionIndex,
                                             Attribute::MinSize))
  thres = HintThreshold;

因此,我们已经从优化级别和其他因素中获得了基线内联阈值,但如果它低于全局HintThreshold,我们会将其提升。 (可以从命令行设置HintThreshold。)

getInlineThreshold()似乎只有one call siteSimpleInliner的成员:

InlineCost getInlineCost(CallSite CS) override {
  return ICA->getInlineCost(CS, getInlineThreshold(CS));
}

它在其成员指针getInlineCost上调用一个名为InlineCostAnalysis的虚方法。

搜索::getInlineCost()以查找属于类成员的版本,我们找到一个属于AlwaysInline的成员 - 这是一个非标准但广泛支持的编译器功能 - 另一个是成员InlineCostAnalysis。它使用Threshold参数here

CallAnalyzer CA(Callee->getDataLayout(), *TTI, AT, *Callee, Threshold);
bool ShouldInline = CA.analyzeCall(CS);

CallAnalyzer::analyzeCall()超过200行和does the real nitty gritty work of deciding if the function is inlineable。它衡量了许多因素,但在我们阅读该方法时,我们发现它的所有计算都要么操纵ThresholdCost。最后:

return Cost < Threshold;

但名为ShouldInline的返回值实际上是用词不当。实际上analyzeCall()的主要目的是在Cost对象上设置ThresholdCallAnalyzer成员变量。返回值仅表示某些其他因素已覆盖成本与阈值分析as we see here的情况:

// Check if there was a reason to force inlining or no inlining.
if (!ShouldInline && CA.getCost() < CA.getThreshold())
  return InlineCost::getNever();
if (ShouldInline && CA.getCost() >= CA.getThreshold())
  return InlineCost::getAlways();

否则,我们返回一个存储CostThreshold的对象。

return llvm::InlineCost::get(CA.getCost(), CA.getThreshold());

因此,在大多数情况下,我们不会返回“是或否”的决定。搜索继续!使用getInlineCost()的返回值在哪里?

真正的决定

It's found in bool Inliner::shouldInline(CallSite CS)。另一大功能。它在开头就调用了getInlineCost()

事实证明getInlineCost分析了内联函数的内在成本 - 它的参数签名,代码长度,递归,分支,链接等等 - 以及一些关于<的集合信息em>使用该函数的每个位置。另一方面,shouldInline()将此信息与有关使用该函数的特定位置的更多数据相结合。

在整个方法中,都会调用InlineCost::costDelta() - 这将使用由InlineCost计算的Threshold s analyzeCall()值。最后,我们返回bool。做出决定。在Inliner::runOnSCC()

if (!shouldInline(CS)) {
  emitOptimizationRemarkMissed(CallerCtx, DEBUG_TYPE, *Caller, DLoc,
                               Twine(Callee->getName() +
                                     " will not be inlined into " +
                                     Caller->getName()));
  continue;
}

// Attempt to inline the function.
if (!InlineCallIfPossible(CS, InlineInfo, InlinedArrayAllocas,
                          InlineHistoryID, InsertLifetime, DL)) {
  emitOptimizationRemarkMissed(CallerCtx, DEBUG_TYPE, *Caller, DLoc,
                               Twine(Callee->getName() +
                                     " will not be inlined into " +
                                     Caller->getName()));
  continue;
}
++NumInlined;

InlineCallIfPossible()根据shouldInline()的决定进行内联。

因此Thresholdinline关键字影响,并在最后用于决定是否内联。

因此,您的Perception B部分错误,因为至少有一个主要编译器会根据inline关键字更改其优化行为。

但是,我们也可以看到inline只是一个提示,其他因素可能会超过它。

答案 1 :(得分:20)

两者都是正确的。

使用inline可能会或可能不会影响编译器内联任何特定函数调用的决定。所以A是正确的 - 它作为一个非绑定请求,调用函数内联,编译器可以自由忽略。

inline的语义效应是放宽一个定义规则的限制,允许在多个翻译单元中使用相同的定义,如B中所述。对于许多编译器,这对于允许内联函数调用是必要的 - 此时必须提供定义,并且编制者只需要一次处理一个翻译单元。