如果我将一个大函数声明为内联函数怎么办?

时间:2014-02-07 16:26:10

标签: c++ performance inline

我搜索了一些相关问题(例如Benefits of inline functions in C++?),但我仍然有疑问。

如果内联函数只是“为编译器提供一个简单的机制来应用更多的OPTIMIZATIONS”。

  1. 那么我可以将每个函数设置为内联函数吗?
  2. 如果我错误地将函数设置为内联函数,会对性能产生什么影响?
  3. 任何告诉我函数大小不应该是内联函数的阈值?

5 个答案:

答案 0 :(得分:7)

仅使用inline来满足一个定义规则。出于性能原因,不要为inline而烦恼。

  

如果内联函数只是为了提供一个简单的机制   编译器应用更多的优化。"

如果我们正在谈论inline关键字,那么 <{1}}的含义是 。就像一样。

所有inline 确保函数不违反单一定义规则。它还可能为编译器提供一个提示,即编译器应该将代码内联,这可能会或可能不会提高速度,但编译器有权忽略该提示。事实上,在现代编译器中,他们会在很多时候忽略这一暗示。

编译器很可能不会内联代码的情况之一是具有大功能。这并不意味着函数不应该被声明为inline,但这完全取决于函数的声明,定义和使用方式。但是,再一次,它与性能没有任何关系

在应用优化技术时,不要试图超越编译器。它使你的程序比你快得多。


让我们看看标准对所有这些内容的评价。

7.1.2函数说明符

  

2 /带有inline说明符的函数声明(8.3.5,9.3,11.3)   声明内联函数inline说明符表示   实现内联函数体的替换   呼叫点优先于通常的函数调用机制。   执行此内联替换不需要实现   在通话点;然而,即使这种内联替换是   省略,7.1.2定义的内联函数的其他规则   仍然受到尊重。

这告诉我们inline是对编译器的内联请求,并且编译器不需要执行该替换。但这也告诉我们,无论编译器执行此内联替换,&#34;其他规则&#34; 7.1.2中定义的仍然适用。

很久以前,C ++编译器采用的优化技术相对于今天的编译器来说是原始的。在那些日子里,使用inline作为优化技术可能是有意义的。但是现在,编译器在优化代码方面要好得多。即使函数没有实际内联,编译器也应用今天使代码比内联更快的技术。 (一个可能的例子,RVO。)

所以最终的结果是,虽然7.1.2 / 2的初衷可能是给程序员一个手动优化技术,但现代编译器如此积极地进行优化,以至于这个原始意图在很大程度上没有实际意义。

所以inline剩下的就是&#34;其他规则。&#34;那么那些其他规则是什么? (C ++ 11 verbiage)

  

4 /应在每个翻译单元中定义内联函数   它使用的次数和定义完全相同   每个案例(3.2)。 [注意:调用内联函数可能是   在其定义出现之前遇到的翻译单元。 -   结束注释]如果函数的定义出现在翻译中   单位在第一次声明为内联之前,该程序是   病态的。如果内联声明了具有外部链接的函数   一个翻译单元,应在所有翻译中内联声明   出现的单位;无需诊断。内联   具有外部链接的功能应具有相同的地址   翻译单位。外部内联中的静态局部变量   函数总是指同一个对象。中的字符串文字   外部内联函数的主体是不同的对象   翻译单位。 [注意:字符串文字出现在默认值中   参数不仅仅是因为内联函数的体内   expression用于来自该内联函数的函数调用。 - 结束   注意]在extern内联函数体内定义的类型是   每个翻译单元都有相同的类型。

让我们来看一个例子。假设我们有这个类inline

文件:foo.h

template

如果我们在#ifndef FOO_H #define FOO_H #include <string> #include <sstream> class StringBuilder { public: template <typename T> inline StringBuilder& operator<<(const T& t) { mStream << t; return * this; } operator std::string () const; private: std::stringstream mStream; }; StringBuilder::operator std::string() const { return mStream.str(); } #endif #include main.cpp并使用StringBuilder,那么一切都很好:

文件:main.cpp

#include <iostream>
#include <string>

int main()
{
    double d = 3.14;
    unsigned a = 42; 

    std::string s = StringBuilder() 
        << "d=" << d << ", a=" << a;
    std::cout << s << "\n";
}

输出:

jdibling@hurricane:~/dev/hacks$ ./hacks 
d=3.14, a=42

但如果我们想在第二个翻译单元中使用StringBuilder,我们就会遇到问题:

文件:other.cpp

#include <iostream>
#include <string>

#include "foo.h"

void DoSomethingElse()
{
    unsigned long l = -12345;
    long l2 = 223344;

    std::string s = StringBuilder()
        << "l=" << l << ", l2=" << l2; 
    std::cout << s << "\n";
}

编译器输出:

ninja: Entering directory `.'
[1/3] Building CXX object CMakeFiles/hacks.dir/main.o
[2/3] Building CXX object CMakeFiles/hacks.dir/other.o
[3/3] Linking CXX executable hacks
FAILED: : && /usr/bin/g++   -Wall -std=c++11 -g   CMakeFiles/hacks.dir/main.o CMakeFiles/hacks.dir/other.o  -o hacks  -rdynamic -lboost_regex-mt && :
CMakeFiles/hacks.dir/other.o: In function `std::operator|(std::_Ios_Openmode, std::_Ios_Openmode)':
/home/jdibling/dev/hacks/foo.h:21: multiple definition of `StringBuilder::operator std::string() const'
CMakeFiles/hacks.dir/main.o:/home/jdibling/dev/hacks/foo.h:21: first defined here
collect2: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.

StringBuilder::operator std::string()定义了两次;一次进入main.cpp并再次进入other.cpp - 这违反了“一个定义规则”。

我们可以通过创建函数inline

来解决这个问题
class StringBuilder
{
public:
    // [...]
    inline operator std::string () const;
//  ^^^^^^
private:
    std::stringstream mStream;
};

编译器输出:

ninja: Entering directory `.'
[1/3] Building CXX object CMakeFiles/hacks.dir/main.o
[2/3] Building CXX object CMakeFiles/hacks.dir/other.o
[3/3] Linking CXX executable hacks

这是有效的,因为现在operator std::string在具有完全相同定义的两个翻译单元上定义。它与直接在声明中定义函数具有相同的效果:

class StringBuilder
{
public:
    operator std::string () const
    {
        return mStream.str();
    }
private:
    std::stringstream mStream;
};

答案 1 :(得分:1)

仅仅因为你使函数内联它并不意味着编译器必须使它内联。内联函数是对编译器的提示,而不是订单。

编译器将使用一组指标,例如优化级别,构建类型(调试或发布),代码大小等来确定是否应该内联函数。

答案 2 :(得分:1)

内联的基本编程规则是函数应少于10行; 但是,还有更多有关编译器如何看待它以及应该考虑哪些因素的信息。

https://en.cppreference.com/w/cpp/language/inline

那我可以将每个函数设置为内联函数吗?

如果将一个非常大的函数设置为内联函数,则将其完全视为编译器调用,如果将其视为内联函数,则对于其他小函数而言,这是正确的。 您不必担心编译器如何优化代码。 相反,优点是存在一些缺点,或者将大函数声明为内联或在.h中定义它们以考虑内联。对于每个内联函数,编译器都会尝试将其放入编译器缓存中,如果函数太大而您强制进行内联,则会出现缓存未命中的情况。

如果我错误地将一个函数设置为内联函数,那么性能会如何?

它是编译器调用,如果它想内联,则可以强制执行,但是, 您在窗口中具有__forceinline来强制内联 为此,也不能保证将内联函数。即使使用__forceinline关键字,也不能强制编译器内联特定函数。使用/ clr进行编译时,如果将安全属性应用于该函数,则编译器不会内联该函数,但应谨慎使用该函数,因为它完全取决于程序员对如何实现该函数的判断。 https://docs.microsoft.com/en-us/cpp/cpp/inline-functions-cpp?view=vs-2019 如果不确定,请不要强行使用它。

有什么阈值可以告诉我什么功能不应该是内联函数?

一个大于10行的函数仍然是需要考虑的基本规则。 默认情况下会发生内联,编译器会根据定义的规则进行优化。

内联的基本规则是在.h中定义小函数,以考虑将它们用于内联。切勿在.h文件中添加大的定义,因为它们确实会成为编译器的开销。

答案 3 :(得分:0)

如果我们假设编译器将内联代码粘贴到调用函数的位置,那么您将拥有大量重复代码。

从功能上讲,您可能会发现性能改善可以忽略不计,但可执行文件的大小却会大幅增加。

让编译器粘贴代码而不是调用函数的主要原因是函数中的代码小于或接近调用函数并从函数返回时的开销成本。经验法则是几个语句的功能应该标记为内联。正如其他人所说,inline关键字是对编译器的提示,而不是请求。

答案 4 :(得分:0)

如果你将函数声明为inline,它最终决定是否设置函数inline,它只是提示编译器来创建这个函数{{1} }。