C ++中的常量和编译器优化

时间:2008-10-17 13:56:36

标签: c++ optimization compiler-construction

我已经阅读了关于C ++中const-correctness的所有建议,并且它(部分)很重要,因为它有助于编译器优化代码。我从未见过的是关于编译器如何使用这些信息来优化代码的一个很好的解释,甚至连好书都没有解释幕后发生的事情。

例如,编译器如何优化声明为const的方法与不应该但应该是的方法。当你引入可变变量时会发生什么?它们会影响const方法的这些优化吗?

12 个答案:

答案 0 :(得分:54)

我认为const关键字主要用于程序语义的编译检查,而非优化。

[{3}}中的Herb Sutter解释了为什么编译器在通过const引用传递参数或者声明const返回值时无法优化任何内容。原因是编译器无法确定引用的对象是否会被更改,即使声明为const:一个可以使用const_cast,或者某些其他代码可以在同一个对象上具有非const引用。 / p>

然而,引用Herb Sutter的文章:

  

只有一个案例在说   “const”真的意味着什么,而且   这就是当对象被设为const时   他们的定义点。在那里面   情况下,编译器可以经常   成功地把这样的“真正的常量”   对象进入只读存储器[...]。

本文还有很多内容,所以我鼓励你阅读它:在此之后你将更好地理解持续优化。

答案 1 :(得分:35)

让我们忽略方法,只看const对象;编译器在这里有更多的优化机会。如果一个对象被声明为const,那么(ISO / IEC 14882:2003 7.1.5.1(4)):

  

除了声明任何类成员   mutable(7.1.1)可以修改,任何   尝试修改const对象   在其一生中(3.8)导致   未定义的行为。

让我们忽略可能具有可变成员的对象 - 编译器可以自由地假设该对象不会被修改,因此它可以产生重要的优化。这些优化可以包括以下内容:

  • 将对象的值直接合并到机器指令操作码
  • 完全消除永远无法访问的代码,因为const对象用于编译时已知的条件表达式
  • 如果const对象控制循环的迭代次数,则循环展开

请注意,这个东西仅适用于实际对象是const的情况 - 它不适用于通过const指针或引用访问的对象,因为这些访问路径可能导致非const的对象(甚至可以很好地定义更改对象虽然是const指针/引用,只要实际对象是非const并且你抛弃了对象访问路径的常量。)

在实践中,我认为没有编译器可以对各种const对象执行任何重要的优化。但对于原始类型的对象(整数,字符等),我认为编译器可以非常积极地进行优化 使用这些物品。

答案 2 :(得分:6)

handwaving开始

基本上,数据修复越早,编译器就可以越多地绕数据的实际分配移动,确保管道不会停滞

结束handwaving

答案 3 :(得分:5)

咩。正确性更像是一种样式/错误检查,而不是优化。完全优化的编译器将遵循变量的使用,并且可以检测变量何时是有效的const。

除此之外,编译器不能依赖于你说实话 - 你可能会在它不知道的库函数中丢弃const。

所以是的,const-correctness是一个值得瞄准的东西,但它并没有告诉编译器任何它不会为自己弄清楚的东西,假设一个好的优化编译器。

答案 4 :(得分:3)

它不会优化声明为const的函数。

它可以优化调用声明为const的函数的函数。

void someType::somefunc();

void MyFunc()
{
    someType A(4);   // 
    Fling(A.m_val);
    A.someFunc();
    Flong(A.m_val);
}

这里要调用Fling,必须将valud A.m_val加载到CPU寄存器中。如果someFunc()不是const,则必须在调用Flong()之前重新加载该值。如果someFunc是const,那么我们可以使用仍在寄存器中的值调用Flong。

答案 5 :(得分:3)

方法作为const的主要原因是const正确性,而不是对方法本身进行可能的编译优化。

如果变量是常数,他们可以(理论上)优化掉。但只有编译器可以看到范围。毕竟编译器必须允许在其他地方用const_cast修改它们。

答案 6 :(得分:2)

这些都是真正的答案,但答案和问题似乎假定一件事:编译器优化实际上很重要。

编译器优化只有一种代码,即代码

  • 一个紧密的内环,
  • 在您编译的代码中,而不是第三方库,
  • 不包含函数或方法调用(甚至是隐藏的函数),
  • 程序计数器花费了大量时间

如果其他99%的代码被优化到N度,它将不会产生差异,因为它只在程序计数器实际花费时间的代码中很重要(可以通过采样找到)。

答案 7 :(得分:1)

如果优化器实际上将大量库存放入const声明中,我会感到惊讶。有很多代码最终会抛弃const-ness,它将是一个非常鲁莽的优化器,它依赖于程序员声明来假设状态可能发生变化。

答案 8 :(得分:1)

const-correctness也可用作文档。如果函数或参数被列为const,我不需要担心从我的代码下面更改的值(除非团队中的其他人非常顽皮)。不过,如果它没有内置到库中,我不确定它是否真的值得。

答案 9 :(得分:0)

const是直接优化的最明显的一点是将参数传递给函数。确保函数不修改数据通常很重要,因此函数签名的唯一真正选择是:

void f(Type dont_modify); // or
void f(Type const& dont_modify);

当然,真正的神奇之处在于传递参考而不是创建对象的(昂贵的)副本。但是如果引用没有标记为const,这将削弱此函数的语义并产生负面影响(例如使错误跟踪变得更难)。因此,const可在此处进行优化。

/编辑:实际上,一个好的编译器可以分析函数的控制流,确定它不会修改参数并进行优化(传递引用而不是副本)本身。 const这里仅仅是对编译器的帮助。但是,由于C ++具有一些相当复杂的语义,并且这种控制流分析对于大型函数来说可能非常昂贵,因此我们可能不应该依赖编译器。有没有人有任何数据支持我/证明我错了?

/ EDIT2:是的,一旦自定义拷贝构造函数开始起作用,它就会变得更加棘手,因为在这种情况下不允许编译器忽略调用它们。

答案 10 :(得分:0)

此代码,

class Test
{
public:
  Test (int value) : m_value (value)
  {
  }

  void SetValue (int value) const
  {
    const_cast <Test&>(*this).MySetValue (value);
  }

  int Value () const
  {
    return m_value;
  }

private:
  void MySetValue (int value)
  {
    m_value = value;
  }

  int
    m_value;
};

void modify (const Test &test, int value) 
{
  test.SetValue (value);
}

void main ()
{
  const Test
    test (100);

  cout << test.Value () << endl;
  modify (test, 50);
  cout << test.Value () << endl;
}

输出:

100
50

这意味着const声明的对象已在const成员函数中被更改。 C ++语言中const_cast(和mutable关键字)的存在意味着const关键字无法帮助编译器生成优化代码。正如我在之前的帖子中指出的那样,它甚至可以产生意想不到的结果。

作为一般规则:

  

const!= optimization

实际上,这是一个合法的C ++修饰符:

volatile const

答案 11 :(得分:-1)

const帮助编译器优化主要是因为它使您编写可优化的代码。除非你投入const_cast