为什么我应该使用内联代码?

发布于 2024-07-05 18:17:42 字数 172 浏览 13 评论 0 原文

我是一名 C/C++ 开发人员,这里有几个始终困扰我的问题。

  • “常规”代码和内联代码之间有很大区别吗?
  • 主要区别是什么?
  • 内联代码只是宏的一种“形式”吗?
  • 选择内联代码时必须进行什么样的权衡?

谢谢

I'm a C/C++ developer, and here are a couple of questions that always baffled me.

  • Is there a big difference between "regular" code and inline code?
  • Which is the main difference?
  • Is inline code simply a "form" of macros?
  • What kind of tradeoff must be done when choosing to inline your code?

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

别念他 2024-07-12 18:17:42

性能

正如之前的答案中所建议的,使用 inline 关键字可以通过内联函数调用来提高代码速度,但通常会以增加可执行文件为代价。 “内联函数调用”只是指在相应地填充参数后,用函数的实际代码替换对目标函数的调用。

然而,当设置为高度优化时,现代编译器非常擅长自动内联函数调用,无需用户提示。 实际上,编译器通常比人类更擅长确定哪些调用内联以提高速度。

为了提高性能而显式声明内联函数(几乎?)总是不必要的!

此外,编译器可以并且将会忽略< /strong> inline 请求(如果适合的话)。 如果对函数的调用无法内联(即使用重要的递归或函数指针),而且如果函数太大而无法获得有意义的性能增益,则编译器将执行此操作。

一条定义规则

但是,使用 inline 关键字 声明内联函数其他效果,并且实际上可能是满足单一定义规则 (ODR) 所必需的:C++ 标准中的这一规则规定,给定符号可以多次声明,但只能定义一次。 如果链接编辑器(=链接器)遇到多个相同的符号定义,它将生成错误。

此问题的一种解决方案是确保编译单元不会通过将给定符号声明为静态来提供内部链接来导出该符号。

然而,通常最好将函数标记为内联。 这告诉链接器跨编译单元将该函数的所有定义合并为一个定义,具有一个地址和共享函数静态变量。

例如,请考虑以下程序:

// header.hpp
#ifndef HEADER_HPP
#define HEADER_HPP

#include <cmath>
#include <numeric>
#include <vector>

using vec = std::vector<double>;

/*inline*/ double mean(vec const& sample) {
    return std::accumulate(begin(sample), end(sample), 0.0) / sample.size();
}

#endif // !defined(HEADER_HPP)
// test.cpp
#include "header.hpp"

#include <iostream>
#include <iomanip>

void print_mean(vec const& sample) {
    std::cout << "Sample with x̂ = " << mean(sample) << '\n';
}
// main.cpp
#include "header.hpp"

void print_mean(vec const&); // Forward declaration.

int main() {
    vec x{4, 3, 5, 4, 5, 5, 6, 3, 8, 6, 8, 3, 1, 7};
    print_mean(x);
}

请注意,两个 .cpp 文件都包含头文件,因此也包含 mean 的函数定义。 尽管文件保存时使用了包含防止双重包含的保护措施,但这将导致同一函数的两个定义,尽管在不同的编译单元中。

现在,如果您尝试链接这两个编译单元 - 例如使用以下命令:

⟩⟩⟩ g++ -std=c++11 -pedantic main.cpp test.cpp

您将收到一条错误消息,指出“重复符号 __Z4meanRKNSt3__16vectorIdNS_9allocatorIdEEEE”(即 我们函数的损坏名称 mean)。

但是,如果您取消注释函数定义前面的 inline 修饰符,则代码将正确编译和链接。

函数模板是一种特殊情况:它们总是内联,无论它们是否以这种方式声明。 这并不意味着编译器将内联调用它们,但它们不会违反 ODR。 对于类或结构内部定义的成员函数也是如此。

Performance

As has been suggested in previous answers, use of the inline keyword can make code faster by inlining function calls, often at the expense of increased executables. “Inlining function calls” just means substituting the call to the target function with the actual code of the function, after filling in the arguments accordingly.

However, modern compilers are very good at inlining function calls automatically without any prompt from the user when set to high optimisation. Actually, compilers are usually better at determining what calls to inline for speed gain than humans are.

Declaring functions inline explicitly for the sake of performance gain is (almost?) always unnecessary!

Additionally, compilers can and will ignore the inline request if it suits them. Compilers will do this if a call to the function is impossible to inline (i.e. using nontrivial recursion or function pointers) but also if the function is simply too large for a meaningful performance gain.

One Definition Rule

However, declaring an inline function using the inline keyword has other effects, and may actually be necessary to satisfy the One Definition Rule (ODR): This rule in the C++ standard states that a given symbol may be declared multiple times but may only be defined once. If the link editor (= linker) encounters several identical symbol definitions, it will generate an error.

One solution to this problem is to make sure that a compilation unit doesn't export a given symbol by giving it internal linkage by declaring it static.

However, it's often better to mark a function inline instead. This tells the linker to merge all definitions of this function across compilation units into one definition, with one address, and shared function-static variables.

As an example, consider the following program:

// header.hpp
#ifndef HEADER_HPP
#define HEADER_HPP

#include <cmath>
#include <numeric>
#include <vector>

using vec = std::vector<double>;

/*inline*/ double mean(vec const& sample) {
    return std::accumulate(begin(sample), end(sample), 0.0) / sample.size();
}

#endif // !defined(HEADER_HPP)
// test.cpp
#include "header.hpp"

#include <iostream>
#include <iomanip>

void print_mean(vec const& sample) {
    std::cout << "Sample with x̂ = " << mean(sample) << '\n';
}
// main.cpp
#include "header.hpp"

void print_mean(vec const&); // Forward declaration.

int main() {
    vec x{4, 3, 5, 4, 5, 5, 6, 3, 8, 6, 8, 3, 1, 7};
    print_mean(x);
}

Note that both .cpp files include the header file and thus the function definition of mean. Although the file is saved with include guards against double inclusion, this will result in two definitions of the same function, albeit in different compilation units.

Now, if you try to link those two compilation units — for example using the following command:

⟩⟩⟩ g++ -std=c++11 -pedantic main.cpp test.cpp

you'll get an error saying “duplicate symbol __Z4meanRKNSt3__16vectorIdNS_9allocatorIdEEEE” (which is the mangled name of our function mean).

If, however, you uncomment the inline modifier in front of the function definition, the code compiles and links correctly.

Function templates are a special case: they are always inline, regardless of whether they were declared that way. This doesn’t mean that the compiler will inline calls to them, but they won’t violate ODR. The same is true for member functions that are defined inside a class or struct.

烟凡古楼 2024-07-12 18:17:42
  • “常规”代码和内联代码之间有很大区别吗?

是和不是。 不,因为内联函数或方法与常规函数或方法具有完全相同的特征,最重要的是它们都是类型安全的。 是的,因为编译器生成的汇编代码会有所不同; 对于常规函数,每次调用都会被转换为几个步骤:将参数压入堆栈、跳转到函数、弹出参数等,而对内联函数的调用将被其实际代码替换,例如宏。

  • 内联代码只是宏的一种“形式”吗?

! 宏是简单的文本替换,可能会导致严重错误。 考虑以下代码:

#define unsafe(i) ( (i) >= 0 ? (i) : -(i) )

[...]
unsafe(x++); // x is incremented twice!
unsafe(f()); // f() is called twice!
[...]

使用内联函数,您可以确定在函数实际执行之前将计算参数。 它们还将接受类型检查,并最终转换为匹配形式参数类型。

  • 选择内联代码时必须做出什么样的权衡?

通常,使用内联函数但二进制代码更大时程序执行应该更快。 有关更多信息,您应该阅读 GoTW#33

  • Is there a big difference between "regular" code and inline code?

Yes and no. No, because an inline function or method has exactly the same characteristics as a regular one, most important one being that they are both type safe. And yes, because the assembly code generated by the compiler will be different; with a regular function, each call will be translated into several steps: pushing parameters on the stack, making the jump to the function, popping the parameters, etc, whereas a call to an inline function will be replaced by its actual code, like a macro.

  • Is inline code simply a "form" of macros?

No! A macro is simple text replacement, which can lead to severe errors. Consider the following code:

#define unsafe(i) ( (i) >= 0 ? (i) : -(i) )

[...]
unsafe(x++); // x is incremented twice!
unsafe(f()); // f() is called twice!
[...]

Using an inline function, you're sure that parameters will be evaluated before the function is actually performed. They will also be type checked, and eventually converted to match the formal parameters types.

  • What kind of tradeoff must be done when choosing to inline your code?

Normally, program execution should be faster when using inline functions, but with a bigger binary code. For more information, you should read GoTW#33.

昔日梦未散 2024-07-12 18:17:42

内联代码本质上类似于宏,但它是实际的代码,可以优化。 非常小的函数通常适合内联,因为与该方法所做的少量实际工作相比,设置函数调用(将参数加载到正确的寄存器中)所需的工作成本很高。 通过内联,无需设置函数调用,因为代码直接“粘贴到”任何使用它的方法中。

内联会增加代码大小,这是其主要缺点。 如果代码太大以至于无法放入 CPU 缓存,则速度可能会大幅下降。 您只需要在极少数情况下担心这一点,因为您不太可能在如此多的地方使用一种方法,增加的代码会导致问题。

总之,内联对于加速多次调用但不在太多位置的小方法来说是理想的选择(不过 100 个位置仍然可以 - 您需要进入相当极端的示例才能获得任何显着的代码膨胀)。

编辑:正如其他人指出的那样,内联只是对编译器的建议。 如果它认为您提出了愚蠢的请求(例如内联一个巨大的 25 行方法),它可以随意忽略您。

Inline code works like macros in essence but it is actual real code, which can be optimized. Very small functions are often good for inlining because the work needed to set up the function call (load the parameters into the proper registers) is costly compared to the small amount of actual work the method does. With inlining, there is no need to set up the function call, because the code is directly "pasted into" any method that uses it.

Inlining increases code size, which is its primary drawback. If the code is so big that it cannot fit into the CPU cache, you can get major slowdowns. You only need to worry about this in rare cases, since it is not likely you are using a method in so many places the increased code would cause issues.

In summary, inlining is ideal for speeding up small methods that are called many times but not in too many places (100 places is still fine, though - you need to go into quite extreme examples to get any significant code bloat).

Edit: as others have pointed out, inlining is only a suggestion to the compiler. It can freely ignore you if it thinks you are making stupid requests like inlining a huge 25-line method.

生生漫 2024-07-12 18:17:42
  • “常规”代码和内联代码之间有很大区别吗?

是的 - 内联代码不涉及函数调用,并将寄存器变量保存到堆栈中。 每次“调用”时它都会使用程序空间。 因此,总的来说,执行时间较短,因为处理器中没有分支,也没有保存状态、清除缓存等。

  • 内联代码只是宏的一种“形式”吗?

宏和内联代码有相似之处。 最大的区别在于内联代码被专门格式化为函数,因此编译器和未来的维护人员有更多选择。 具体来说,如果您告诉编译器优化代码空间,或者未来的维护者最终扩展它并在代码中的许多地方使用它,那么它可以很容易地变成一个函数。

  • 选择内联代码时必须进行什么样的权衡?

    • 宏:代码空间占用高,执行速度快,如果“函数”太长则难以维护
    • 功能:代码空间占用低,执行速度较慢,易于维护
    • 内联函数:代码空间占用高、执行速度快、易于维护

需要注意的是,寄存器保存和跳转到函数确实会占用代码空间,因此对于非常小的函数,内联可以比函数占用更少的空间。

-Adam

  • Is there a big difference between "regular" code and inline code?

Yes - inline code does not involve a function call, and saving register variables to the stack. It uses program space each time it is 'called'. So overall it takes less time to execute because there's no branching in the processor and saving of state, clearing of caches, etc.

  • Is inline code simply a "form" of macros?

Macros and inline code share similarities. the big difference is that the inline code is specifically formatted as a function so the compiler, and future maintainers, have more options. Specifically it can easily be turned into a function if you tell the compiler to optimize for code space, or a future maintainer ends up expanding it and using it in many places in their code.

  • What kind of tradeoff must be done when choosing to inline your code?

    • Macro: high code space usage, fast execution, hard to maintain if the 'function' is long
    • Function: low code space usage, slower to execute, easy to maintain
    • Inline function: high code space usage, fast execution, easy to maintain

It should be noted that the register saving and jumping to the function does take up code space, so for very small functions an inline can take up less space than a function.

-Adam

蔚蓝源自深海 2024-07-12 18:17:42

这取决于编译器...
假设你有一个愚蠢的编译器。 通过指示函数必须内联,它会在每次调用该函数时放置该函数内容的副本。

优点:没有函数调用开销(放入参数、压入当前PC、跳转到函数等)。 例如,在大循环的中心部分可能很重要。

不便:使生成的二进制文件膨胀。

是宏吗? 不是真的,因为编译器仍然检查参数的类型等。

那么智能编译器呢? 如果他们“感觉”函数太复杂/太大,他们可以忽略内联指令。 也许它们可以自动内联一些琐碎的函数,比如简单的 getter/setter。

It depends on the compiler...
Say you have a dumb compiler. By indicating a function must be inlined, it will put a copy of the content of the function on each occurrence were it is called.

Advantage: no function call overhead (putting parameters, pushing the current PC, jumping to the function, etc.). Can be important in the central part of a big loop, for example.

Inconvenience: inflates the generated binary.

Is it a macro? Not really, because the compiler still checks the type of parameters, etc.

What about smart compilers? They can ignore the inline directive, if they "feel" the function is too complex/too big. And perhaps they can automatically inline some trivial functions, like simple getters/setters.

烟花肆意 2024-07-12 18:17:42

内联与宏的不同之处在于,它是对编译器的提示(编译器可能决定不内联代码!),而宏是在编译之前生成源代码文本,因此“强制”内联。

Inline differs from macros in that it's a hint to the compiler (compiler may decide not to inline the code!) and macros are source code text generation before the compilation and as such are "forced" to be inlined.

寂寞笑我太脆弱 2024-07-12 18:17:42

将函数标记为内联意味着编译器可以选择将其包含在调用该函数的“内联”位置(如果编译器选择这样做); 相比之下,宏总是就地展开。 内联函数将设置适当的调试符号,以允许符号调试器跟踪其来源,而调试宏则令人困惑。 内联函数必须是有效的函数,而宏……好吧,不是。

决定内联声明一个函数很大程度上是一种空间权衡——如果编译器决定内联它,您的程序将会更大(特别是如果它不是静态的,在这种情况下,至少需要一个非内联副本供使用)任何外部物体); 事实上,如果函数很大,这可能会导致性能下降,因为缓存中的代码较少。 然而,总体性能提升只是消除了函数调用本身的开销; 对于作为内循环一部分调用的小函数,这是一个有意义的权衡。

如果您信任您的编译器,请随意标记内部循环中使用的小函数inline; 编译器将负责在决定是否内联时做正确的事情。

Marking a function inline means that the compiler has the option to include in "in-line" where it is called, if the compiler chooses to do so; by contrast, a macro will always be expanded in-place. An inlined function will have appropriate debug symbols set up to allow a symbolic debugger to track the source where it came from, while debugging macros is confusing. Inline functions need to be valid functions, while macros... well, don't.

Deciding to declare a function inline is largely a space tradeoff -- your program will be larger if the compiler decides to inline it (particularly if it isn't also static, in which case at least one non-inlined copy is required for use by any external objects); indeed, if the function is large, this could result in a drop in performance as less of your code fits in cache. The general performance boost, however, is just that you're getting rid of the overhead of the function call itself; for a small function called as part of an inner loop, that's a tradeoff that makes sense.

If you trust your compiler, mark small functions used in inner loops inline liberally; the compiler will be responsible for Doing The Right Thing in deciding whether or not to inline.

独夜无伴 2024-07-12 18:17:42

如果您在 C++ 中将代码标记为内联,您还告诉编译器该代码应该内联执行,即。 该代码块将“或多或少”被插入到被调用的地方(从而消除堆栈上的推入、弹出和跳转)。 所以,是的......如果这些功能适合这种行为,则建议使用。

If you are marking your code as inline in f.e. C++ you are also telling your compiler that the code should be executed inline, ie. that code block will "more or less" be inserted where it is called (thus removing the pushing, popping and jumping on the stack). So, yes... it is recommended if the functions are suitable for that kind of behavior.

一袭水袖舞倾城 2024-07-12 18:17:42

“inline”就像 2000 年的“register”。 不用担心,编译器可以比您更好地决定优化哪些内容。

"inline" is like the 2000's equivalent of "register". Don't bother, the compiler can do a better job of deciding what to optimize than you can.

℡Ms空城旧梦 2024-07-12 18:17:42

通过内联,编译器在调用点插入函数的实现。
您正在做的就是消除函数调用开销。
但是,不能保证所有内联候选对象实际上都会被编译器内联。 然而,对于较小的函数,编译器总是内联的。
因此,如果您有一个被调用多次但只有有限数量的代码(几行)的函数,您可以从内联中受益,因为函数调用开销可能比函数本身的执行时间更长。

内联的一个很好的候选者的典型例子是简单具体类的 getter。

CPoint
{
  public:

    inline int x() const { return m_x ; }
    inline int y() const { return m_y ; }

  private:
    int m_x ;
    int m_y ;

};

某些编译器(例如 VC2005 )具有主动内联选项,并且在使用该选项时不需要指定“inline”关键字。

By inlining, the compiler inserts the implementation of the function, at the calling point.
What you are doing with this is removing the function call overhead.
However, there is no guarantee that your all candidates for inlining will actually be inlined by the compiler. However, for smaller functions, compilers always inline.
So if you have a function that is called many times but only has a limited amount of code - a couple of lines - you could benefit from inlining, because the function call overhead might take longer than the execution of the function itself.

A classic example of a good candidate for inlining are getters for simple concrete classes.

CPoint
{
  public:

    inline int x() const { return m_x ; }
    inline int y() const { return m_y ; }

  private:
    int m_x ;
    int m_y ;

};

Some compilers ( e.g. VC2005 ) have an option for aggressive inlining, and you wouldn't need to specify the 'inline' keyword when using that option.

小忆控 2024-07-12 18:17:42

我不会重申上面的内容,但值得注意的是,虚拟函数不会被内联,因为调用的函数是在运行时解析的。

I won't reiterate the above, but it's worth noting that virtual functions will not be inlined as the function called is resolved at runtime.

掀纱窥君容 2024-07-12 18:17:42

内联通常在优化级别 3 启用(对于 GCC,为 -O3)。 在某些情况下(如果可能的话),这可以显着提高速度。

程序中的显式内联可以提高速度,但代价是增加代码大小。

您应该看看哪个是合适的:代码大小或速度,并决定是否应该将其包含在您的程序中。

您可以只打开第 3 级优化并忘记它,让编译器完成他的工作。

Inlining usually is enabled at level 3 of optimization (-O3 in case of GCC). It can be a significant speed improvement in some cases (when it is possible).

Explicit inlining in your programs can add some speed improvement with the cost of an incresed code size.

You should see which is suitable: code size or speed and decide wether you should include it in your programs.

You can just turn on level 3 of optimization and forget about it, letting the compiler do his job.

为人所爱 2024-07-12 18:17:42

是否应该内联的答案归结为速度。
如果您处于调用函数的紧密循环中,并且它不是一个超级大的函数,而是一个在调用该函数时浪费了大量时间的函数,那么使该函数内联,您将获得很多好处你的钱。

The answer of should you inline comes down to speed.
If you're in a tight loop calling a function, and it's not a super huge function, but one where a lot of the time is wasted in CALLING the function, then make that function inline and you'll get a lot of bang for your buck.

你丑哭了我 2024-07-12 18:17:42

首先,内联是要求编译器内联该函数。因此,是否内联取决于编译器。

  1. 何时使用?何时使用函数
    很少的行(对于所有访问器
    和变异器)但不适用于递归
    函数的
  2. 优点?不涉及调用函数调用所花费的时间
  3. 编译器是否内联其自己的任何函数?是的,当函数在类内的头文件中定义时

First of all inline is a request to compiler to inline the function .so it is upto compiler to make it inline or not.

  1. When to use?When ever a function is
    of very few lines(for all accessors
    and mutator) but not for recursive
    functions
  2. Advantage?Time taken for invoking the function call is not involved
  3. Is compiler inline any function of its own?yes when ever a function is defined in header file inside a class
物价感观 2024-07-12 18:17:42

内联是一种提高速度的技术。 但请使用分析器根据您的情况进行测试。 我发现(MSVC)内联并不总是能够实现,而且当然也不会以任何引人注目的方式实现。 运行时间有时会减少几个百分点,但在略有不同的情况下会增加几个百分点。

如果代码运行缓慢,请拿出分析器来查找问题点并进行处理。

我已经停止向头文件添加内联函数,它增加了耦合,但几乎没有回报。

inlining is a technique to increase speed. But use a profiler to test this in your situation. I have found (MSVC) that inlining does not always deliver and certainly not in any spectacular way. Runtimes sometimes decreased by a few percent but in slightly different circumstances increased by a few percent.

If the code is running slowly, get out your profiler to find troublespots and work on those.

I have stopped adding inline functions to header files, it increases coupling but gives little in return.

遮云壑 2024-07-12 18:17:42

内联代码速度更快。 不需要执行函数调用(每个函数调用都会花费一些时间)。 缺点是您无法传递指向内联函数的指针,因为该函数并不真正作为函数存在,因此没有指针。 此外,该函数不能导出到公共(例如,库中的内联函数在链接到库的二进制文件中不可用)。 另一个问题是,如果您从不同的位置调用该函数,二进制文件中的代码部分将会增长(因为每次都会生成该函数的副本,而不是只有一个副本并总是跳转到那里)

通常您不必手动决定是否内联函数。 例如,GCC 将根据优化级别 (-Ox) 和其他参数自动决定。 它将考虑诸如“功能有多大?”之类的因素。 (指令数量)、代码中调用的频率、通过内联二进制文件会变大多少以及其他一些指标。 例如,如果一个函数是静态的(因此不会导出)并且只在代码中调用一次,并且您从不使用指向该函数的指针,那么 GCC 很可能会决定自动内联它,因为它不会产生负面影响(仅内联一次二进制文件不会变得更大)。

Inline code is faster. There is no need to perform a function call (every function call costs some time). Disadvantage is you cannot pass a pointer to an inline function around, as the function does not really exist as function and thus has no pointer. Also the function cannot be exported to public (e.g. an inline function in a library is not available within binaries linking against the library). Another one is that the code section in your binary will grow, if you call the function from various places (as each time a copy of the function is generated instead of having just one copy and always jumping there)

Usually you don't have to manually decide if a function shall be inlined or not. E.g. GCC will decide that automatically depending on optimizing level (-Ox) and depending on other parameters. It will take things into consideration like "How big is the function?" (number of instructions), how often is it called within the code, how much the binary will get bigger by inlining it, and some other metrics. E.g. if a function is static (thus not exported anyway) and only called once within your code and you never use a pointer to the function, chances are good that GCC will decide to inline it automatically, as it will have no negative impact (the binary won't get bigger by inlining it only once).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文