C++小函数没有内联
C++ small function not inlining
我的C++17应用程序中有一个热关键路径函数(根据perf record
,大约占cycles:ppp
的45%),它没有像我预期的那样内联。这是一个很小的函数——它只是返回一个原子指针成员的值。反汇编确认该函数仅为四个汇编指令,包括retq
。此外,在整个构建中,该函数只有一个调用方。我甚至已经将这个函数声明为__attribute__((always_inline))
。然而,正在生成一个对该函数的调用和返回。
调用者在文件A中,被调用者在文件B中。
一些附加说明:
- 我正在使用
-O3
和-march=native
进行编译 - 被调用者声明为
const
,并且不访问任何静态成员 - 链接时我正在通过
-flto
进行链接类型优化 - 我正在使用icc(icc)19.0.3.199进行编译
- 这些函数是简单的成员函数,是
const
,而不是模板,所有这些函数都有十几条或更少的x86汇编指令
实际上,我已经简化了一点——实际上,在我的应用程序中,有两个地方缺少内联。文件B有一个函数F1,它调用文件a的F2,调用文件B的F3(F2和F3是上面列出的)。
文件A:
F2() {
F3();
}
文件B:
F1() {
F2();
}
F3() {}
如何将所有这些内联到一个函数中?另一个更基本的问题是:在不同的文件中定义的函数是否可以内联(也许使用LTO)?
PS
always_inline
属性可能并不代表您所认为的含义。通常,当没有打开优化时,g++不会内联任何内容(我认为这会使调试更容易)。通过添加此属性(always_inline
),编译器将在未优化时内联(可能不是您想要的),但这不会使未内联(able)的函数变成可以或将要内联(ed)的函数。
请参阅:https://gcc.gnu.org/onlinedocs/gcc/Inline.html
根据您的意见,您有以下内容:
文件A.h
void F2();
文件B.h
void F1();
void F3() __attribute__((always_inline));
文件A.cpp
#include "A.h"
#include "B.h"
void F2() {
F3();
}
文件B.cpp
#include "B.h"
#include "A.h"
void F1() {
F2();
}
void F3() {}
在未来,这将是你应该提交的最低可行的申请,因为它有所有的类型信息,足以重建你的情况。
你提供的代码是不可编译的,需要大量的认知负荷才能将你提供的英语描述分解为可编译的代码。
如果您已经设置了编译器,那么可以将F3()
内联到A.cpp
中,但情况可能并不总是如此。要进行这种优化,翻译单元必须能够访问F3()
的源,或者您必须能够跨翻译单元进行优化。
您可以通过将F3()
的主体移动到头文件中来简化此过程。然后它将可以直接内联到翻译单元。
文件A.h
void F2();
文件B.h
void F1();
void F3() __attribute__((always_inline)); // I would not add this.
// Let the compiler not inline in debug mode.
inline void F3() {}
文件A.cpp
#include "A.h"
#include "B.h"
void F2() {
F3();
}
文件B.cpp
#include "B.h"
#include "A.h"
void F1() {
F2();
}
使inline
函数实际内联的标准方法是在同一转换单元中定义它们(例如,在头文件中),并使用inline
说明符。
标准C++中没有关于跨翻译单元内联函数的规定,但有时它可以由编译器作为LTO(IPO,WPO)扩展的一部分来完成。
ICC称之为过程间优化(IPO),您要查找的编译标志是-ipo
。
另请参阅使用IPO。
注意:还有-inline-level=2
,但从-O2
开始已经设置
一些编译器(GCC、ICC,但不是Clang)在构建共享对象(-fPIC
标志)时,永远不会在ELF目标上内联具有公共可见性的函数。这是因为主可执行文件中的函数可能会被新函数所取代。
如果您希望它们内联,您可以尝试以下操作:
- 将函数放入头文件中,然后使用
inline
- 对于GCC,请使用
-fno-semantic-interposition
标志来禁用此行为 - 通过
-fvisibility=protected
或__attribute__((visibility("protected")))
使用受保护的可见性。这并不总是有效的,因为这可能会使编译器生成链接器无法处理的重新定位 - 如果这些函数不需要在共享对象之外可见,请使用专用(
-fvisibility=hidden
)可见性
-fsemantic-interposition
上的GCC文档应该说明一些事情:
一些对象格式,如ELF,允许通过动态链接器。这意味着,对于从DSO导出的符号编译器无法执行过程间传播、内联和预期中的函数或变量这个问题可能会改变。虽然此功能很有用,例如通过调试实现重写内存分配函数在代码质量方面是昂贵的。
注意关于副作用的部分,它解释了-fno-semantic-interposition
如何提供与inline
:类似的编译器保证
使用-fno语义插入,编译器假设如果函数发生插入,覆盖函数将具有完全相同的语义(和副作用)类似地,如果发生插入对于变量,变量的构造函数将是相同的。这个标志对于显式内联声明的函数(在从不允许插入来更改语义)和符号明确声明为弱。
您也可以在此处查看我的答案。
"内联函数是在头中定义的,因为为了内联函数调用,编译器必须能够看到函数体。为了实现这一点,初级编译器必须将函数体与调用放在同一个翻译单元中。"翻译单元指的是一个源文件及其头,这些文件可编译为一个编译单元。因此,在这种情况下,您可以尝试在同一源文件或包含的标头中声明该函数。
- "error: no matching function for call to"构造函数错误
- 什么时候调用组成单元对象的析构函数
- 继承函数的重载解析
- 为什么随机数生成器不在void函数中随机化数字,而在main函数中随机化
- C++模板来检查友元函数的存在
- 递归函数计算序列中的平方和(并输出过程)
- 对RValue对象调用的LValue ref限定成员函数
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 在C++STL中是否有Polyval(Matlab函数)等价物?
- 为什么使用 "this" 指针调用派生成员函数?
- 将对象数组的引用传递给函数
- 函数调用中参数的顺序重要吗
- 函数向量_指针有不同的原型,我可以构建一个吗
- 使用不带参数的函数访问结构元素
- 代码在main()中运行,但在函数中出现错误
- 内置函数可查看CPP中的成员变量
- 如何获取std::result_of函数的返回类型
- 如何在c++中为模板函数实例创建快捷方式
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗