c++ - "inline"关键字与 "inlining"概念

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

我问这个基本问题是为了让记录更正。已转介 this questionits currently accepted answer ,这没有说服力。然而second most voted answer提供更好的洞察力,但也不完美。

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

这是我的看法:

内联概念

这样做是为了节省函数的调用开销。它更类似于宏样式代码替换。没什么好争论的。
inline关键词

知觉A

The inline keyword is a request to the compiler usually used for smaller functions, so that compiler can optimize it and make faster calls. The Compiler is free to ignore it.



我对此提出异议,原因如下:
  • 较大的递归函数不会内联,编译器会忽略 inline关键词。
  • 无论 inline 是什么,优化器都会自动内联较小的函数。关键字是否被提及。

  • 很明显,用户对使用关键字 inline 的函数内联没有任何控制权。 .

    知觉B

    inline has nothing to do with the concept of inlining. Putting inline ahead of big / recursive function won't help and smaller function won't need it, for being inlined.

    The only deterministic use of inline is to maintain the One Definition Rule.



    即如果一个函数是用 inline 声明的那么只有以下事情是强制性的:
  • 即使在多个翻译单元中找到它的主体(例如,在多个 .cpp 文件中包含该头文件),编译器也只会生成 1 个定义并避免多个符号链接(symbolic link)器错误。 (注意:如果该函数的主体不同,则它是未定义的行为。)
  • inline的 body 函数必须在使用它的所有翻译单元中可见/可访问。换句话说,声明一个 inline函数在 .h并在任何一个中定义 .cpp文件将导致其他 .cpp 的“ undefined symbol 链接器错误”文件

  • 判决书

    “A”的看法是完全错误的,“B”的看法是完全正确的。

    标准中对此有一些引用,但是我期待一个从逻辑上解释该判决是对还是错的答案。

    最佳答案

    我不确定你的主张:

    Smaller functions are automatically "inlined" by optimizer irrespective of inline is mentioned or not... It's quite clear that the user doesn't have any control over function "inlining" with the use of keyword inline.



    我听说编译器可以随意忽略您的 inline要求,但我不认为他们完全无视它。

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

    搜索

    搜索词 inlinethe Clang repository导致 token 说明符 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 function , isInlineSpecified() :
    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 到 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 site , SimpleInliner成员(member):
    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()的主要目的是设置CostThreshold CallAnalyzer上的成员变量目的。返回值仅指示其他因素覆盖成本与阈值分析的情况,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();
    

    否则,我们返回一个存储 Cost 的对象。和 Threshold .
    return llvm::InlineCost::get(CA.getCost(), CA.getThreshold());
    

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

    真正的决定

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

    原来getInlineCost分析内联函数的内在成本——它的参数签名、代码长度、递归、分支、链接等——以及有关使用该函数的每个地方的一些聚合信息。另一方面,shouldInline()将此信息与有关使用该功能的特定位置的更多数据相结合。

    在整个方法中有对 InlineCost::costDelta() 的调用。 - 将使用 InlineCost s ThresholdanalyzeCall() 计算出的值.最后,我们返回一个 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()的决定。

    所以Threshold受到 inline 的影响关键字,最后用来决定是否内联。

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

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

    关于c++ - "inline"关键字与 "inlining"概念,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27042935/

    相关文章:

    c++ - 我可以从 CUDA 内核函数调用 __device__ 函数吗?

    c++ - 是依赖类型依赖的非静态数据成员的非限定名称

    c++ - 使用 `&&` 和 `const` 限定符重载 operator== 会导致 C++20 中的歧义

    c++ - 从 WinHTTP 中的 CRYPT_BIT_BLOB 获取 RSA 公钥?

    python - 在 python 中向量化嵌套 for 循环以实现依赖于索引的函数

    c++ - QueryPerformanceCounter 和奇怪的结果

    c - 将 sendfile() 与 SSL 结合使用时出现问题

    c - 指针运算

    c++ - 对齐和SSE异常行为

    c++ - [dcl.constexpr]/3 中的 contain 是什么意思