C ++中的常量和编译器优化
我已经阅读了C ++中关于const正确性的所有build议,并且这很重要(部分),因为它有助于编译器优化代码。 我从来没有见过的是对编译器如何使用这些信息来优化代码的一个很好的解释,甚至好的书也不去解释幕后发生的事情。
例如,编译器如何优化一个声明为const的方法,而不是一个应该是的方法。 当你引入可变variables时会发生什么? 他们影响const方法的优化吗?
让我们忽略方法,只看const对象; 编译器在这里有更多的优化机会。 如果一个对象被声明为const,那么(ISO / IEC 14882:2003 7.1.5.1(4)):
除了可以修改声明为可变的任何类成员(7.1.1)外,任何在其生命周期(3.8)中修改const对象的尝试都会导致未定义的行为。
让我们忽略可能具有可变成员的对象 – 编译器可以自由地假定对象不会被修改,因此可以产生显着的优化。 这些优化可以包括如下内容:
- 将对象的值直接合并到机器指令操作码中
- 完全消除由于const对象在编译时已知的条件expression式中使用而永远无法达到的代码
- 循环展开如果const对象正在控制循环的迭代次数
请注意,这个东西只适用于实际对象是const的 – 它不适用于通过const指针或引用访问的对象,因为这些访问path可能会导致非常量的对象(它甚至可以很好地定义为通过const指针/引用,只要实际的对象是非const的,并且将访问path的常量转换为对象)。
在实践中,我不认为有编译器对所有types的const对象进行任何显着的优化。 但对于原始types的对象(整数,字符等),我认为编译器可以在优化这些项目的使用方面相当积极。
我认为const关键字主要是为了程序语义的编译检查而引入的,而不是为了优化。
在GotW#81文章中 ,Herb Sutter很好地解释了为什么编译器在通过const引用传递参数或者声明const返回值时不能优化任何东西。 原因是编译器没有办法确定被引用的对象是不会被改变的,即使声明了const:一个可以使用const_cast,或者其他一些代码可以在同一个对象上有一个非const引用。
但是,引用香草萨特的文章:
只有一种情况说“const”可以真正意义上的东西,也就是说当对象被定义为const时。 在这种情况下,编译器通常可以成功地将这些“真正的const”对象放到只读存储器中。
在这篇文章中还有很多内容,所以我鼓励你阅读:在这之后,你将会更好地理解不断的优化。
handwaving开始
从本质上讲,数据越早固定,编译器就可以越移动数据的实际分配,确保stream水线不会停顿
结束手动
咩。 与正确性相比,Const-correctness更像是一种风格/错误检查。 全function优化编译器将遵循variables的使用情况,并可以检测variables何时是有效的常量。
除此之外,编译器不能依靠你告诉它的事实 – 你可能会抛出一个库函数,它不知道的const。
所以是的,const-correctness是一个值得追求的目标,但是它并没有告诉编译器它不会自己弄明白,假设一个好的优化编译器。
它不会优化被声明为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。
将方法设为const的主要原因是const正确性,而不是方法本身可能的编译优化。
如果variables是常量,它们可以(理论上)被优化掉。 但是编译器只能看到范围。 毕竟编译器必须允许在其他地方用const_cast修改它们。
这些都是真实的答案,但答案和问题似乎假定了一件事情:编译器优化实际上很重要。
编译器优化只有一种代码,即在代码中
- 一个紧密的内部循环,
- 在您编译的代码中,与第三方库相反,
- 不包含函数或方法调用(甚至隐藏的),
- 程序柜台花费了相当一部分时间
如果其他99%的代码被优化到第N级,它就不会产生差异,因为它只在程序计数器实际花费时间的代码上(可以通过采样find)。
如果优化器真的把很多股票放入一个const声明中,我会感到惊讶。 有很多代码将最终抛弃const,这是一个非常鲁莽的优化器,当状态可能发生变化时,依靠程序员声明来进行假设。
作为文档,常量正确性也是有用的。 如果一个函数或参数被列为const,我不需要担心从我的代码中改变的值(除非团队中的其他人非常调皮)。 但是,如果它不是内置在图书馆中,我不确定这实际上是否值得。
const
是直接优化的最明显的一点是将parameter passing给一个函数。 确保函数不修改数据通常很重要,所以函数签名的唯一真正select是:
void f(Type dont_modify); // or void f(Type const& dont_modify);
当然,这里真正的魔法是通过参考而不是创build一个(昂贵的)对象的副本。 但是如果引用没有被标记为const
,这会削弱这个函数的语义,并且会产生负面影响(比如使得错误跟踪更难)。 因此, const
在这里启用优化。
/编辑:实际上,一个好的编译器可以分析函数的控制stream,确定它不会修改参数并进行优化(通过引用而不是副本)本身。 const
这里只是编译器的帮助。 但是,由于C ++有一些相当复杂的语义,而且这种控制stream分析对于大function来说可能是非常昂贵的,所以我们可能不应该依赖编译器。 有人有任何数据支持我/certificate我错了吗?
/ EDIT2:是的,一旦自定义拷贝构造函数发挥作用,它变得更加棘手,因为在这种情况下不幸的是编译器不允许忽略它们的调用。
这个代码,
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!=优化
事实上,这是一个合法的C ++修饰符:
volatile const
const
帮助编译器进行优化,主要是因为它可以编写优化的代码。 除非你抛出const_cast
。