内联一个操作堆上数据的函数

时间:2017-05-12 14:10:51

标签: c++ multithreading inline compiler-optimization heap-memory

我正在努力优化大多数对象在堆上分配的代码。

我想要了解的是:如果/为什么编译器可能内联可能操纵堆上数据的函数调用。

为了使事情更清楚,假设您有以下代码:

class A
{
public: 
   void foo() // non-const function
   {
     // modify data
     i++;        
     ...
   }
private:    
  int i; 
  // can be anything here, including pointers      
};

int main()
{
  A a; // allocate something on stack
  auto ptr = std::make_unique<A>(); // allocate something on heap

  a.foo();  // case 1
  ptr->foo(); // case 2
  return 0;
}

a.foo()没有内联ptr->foo()时是否可能会内联?

我的猜测是,这可能与编译器无法保证堆上的数据不会被另一个线程修改的事实有关。但是,我不明白是否/为什么它会对内联产生任何影响。

假设没有虚拟功能

编辑:我想我的问题部分是理论上的。假设您正在实现编译器,您能否想到在优化ptr->foo()时不优化a.foo()的任何合理理由?

2 个答案:

答案 0 :(得分:2)

  

我的猜测是,这可能与编译器无法保证堆上的数据不会被另一个线程修改的事实有关。但是,我不明白是否/为什么它会对内联产生任何影响。

这不相关。内联函数和“常规”函数调用对堆具有相同的效果。 无论是否内联,实现都在代码段中。

  

是否有可能a.foo()内联而ptr-&gt; foo()没有?

极不可能。如果编译器可以看到实现并且编译器认为它有用,那么这两个调用都可能会被内联。 我在代码中多次使用“case 2”,并且总是使用g ++内联。

虽然它主要是特定于实现的,但与使用on stack对象调用(在你已经提到的虚函数旁边)相比,没有限制指针函数调用的实际限制。

您应该注意,生成的内联代码可能仍然不同。案例2必须首先确定会对性能产生影响的实际地址,但从那时起它应该几乎相同。

答案 1 :(得分:1)

  

if / why为什么编译器可能没有内联可能操纵堆上数据的函数调用。

编译器可以inline函数调用(可能在devirtualization之后决定)。内联决策是编译器的自由(所以inline关键字,如register,经常被忽略以做出优化决策。编译器通常会决定内联(或不内联)每个特定的调用(所以每个出现的被调用函数名称)。

  

假设您正在实施编译器,您能想到在优化ptr->foo()时不优化a.foo()的任何合理理由吗?

这很容易。通常,(在其他标准中)根据先前内联嵌套函数调用的深度或根据扩展内部表示的当前大小来确定内联。因此,确实会发生ptr->foo()的特定事件(例如,因为它出现在一个小函数中),但不会内联另一个a.foo()

请记住,通常会在每个呼叫站点进行内联决策。在某些编译器上,编译器使用的阈值可能会有所不同或可以调整。

但是内联并不总能加快执行时间(因为CPU cachebranch predictor问题,以及许多其他谜......),这又是另一个有时编译器不会内联特定调用的原因。

对于GCC编译器,请阅读inline functions和各种optimization options(请注意-finline-limit=100-finline-limit=200将提供不同的内联决定;您甚至可以使用不同的--params选项; MILEPOST GCC项目使用机器学习技术来调整这些......)。

也许有些编译器可以更容易地对堆栈分配的数据进行虚拟化(我真的不知道,编译器正在这些问题上取得进展)。这可能是(也许!)堆与堆栈分配可能影响内联决策的原因。