来源:http://www.gotw.ca/gotw/081.htm
const
和返回值声明为const
,是否会对编译器优化代码产生影响? // Example 1
//
const Y& f( const X& x )
{
// ... do something with x and find a Y object ...
return someY;
}
简要地说,也许并不会。
编译器能做的更好的是什么?它能够避免传参和返回值的拷贝开销吗?并不,因为参数和返回值是通过引用来进行传递的。它能够将x
和someY
拷贝到只读内存区域吗?也不会。对于x
和someY
而言,它们的生命周期均在该函数的作用域之外。即便someY
是在函数内部动态创建的,它的所有权也会被传递给调用者。
但在f()
的内部,有没有可能会对代码进行一定的优化呢?因为const
的存在,会不会在某种程度上导致编译器生成更加优化的代码呢?
const
为什么会对编译器的代码优化产生影响回到Example 1
,使用const
修饰参数不会导致编译器对代码产生额外优化的原因是,即便调用一个const
类成员函数,编译器也没法认定x
和someY
的值不会发生改变。再者,除非编译器进行全局优化,不然会有其他的一些问题存在:首先,编译器并不知道其他的代码会不会持有对象x
和someY
的非const
引用,这些变量可能会在f()
执行期间被意外地使用。其次,编译器也不一定会知道,x
和xomeY
所引用的对象,是否是被主动声明为一个const
变量。
仅仅因为x
和y
被声明为const
常量,也不一定意味它们物理上的位是常量。因为这些类可能会存在可变成员,或者其他等价的东西,即在成员函数内部进行const_cast
强制转换或者C
语言样式的强制转换,导致被声明为const
常量并不是一个真正的常量。
只有在变量定义的时候,声明为const
常量,此时说const
常量才具有意义。特别是这些变量是能够在编译期间就生成的POD
类型时,编译器可以将这些真正的const
常量对象放置到只读内存中,同时会被存储到可执行文件本身。这样的对象会被称为ROM-able
// Example 3
//
void f( const Z z )
{
// ...
}
有三个小问题需要思考:
Z
是否可以使编译器生成不一样且更好的代码?Z
是否可以使编译器生成不一样且更好的代码?简要回答就是:能,也不能。
先说一下能的部分。因为编译器知道z
是一个真正的常量对象,所以可以在没有进行全局优化的情况下,进行一些有用的优化。例如,如果在f()
内有一个这样的调用:g(&z)
。编译器可以明确地知道在调用g()
的时候,z
不会发生变化。
除此之外,在参数值声明为const
常量,对于大多数的z
而言,并不会产生任何形式的优化。在这种情况下的优化,并不是编译器代码生成优化。
对于编译器代码生成优化,问题主要归结为编译器是否可以省略拷贝构造,或者是否可以将z
放入只读内存中。这意味着,对象z
在f()
函数内部不会受到任何的影响,所以我们可以直接使用函数外部的对象而不需要多一次的拷贝赋值操作。或者即便需要拷贝,也可以直接将拷贝后的对象直接放置在只读内存中,用到的时候直接读取。
一般情况下,编译器不会因为参数是const
的就主动消除拷贝构造函数。如同上文所提到的一样,这可能会导致太多的问题。尤其是Z
拥有可变成员的时候,或者在其他地方直接或间接使用到Z
的成员函数时,可能会使用const_casts
来改变常量。
但有一些办法来帮助编译器生成更好的代码,如下几点:
Z
的拷贝构造函数和所有在f()
中直接或间接用到的成员函数都是可见的。满足上述三点会让编译器明确每一步具体发生了什么,所以编译器能够在as-if
规则的限制下,自主选择消除一些不必要的代码。
另外,还有一点值得一提。有些人会说编译器执行全局优化的时候,可以对const
常量提供优化以生成更好的代码。事实上,即便去掉const
修饰,也可以对生成代码进行优化。不要介意全局优化的频次低且代价高。这里真正的问题是全局优化清楚所有对象的真正使用情况,所以不管有无声明为const
,都会执行相同的优化操作。全局优化针对的是你真正的操作,而不是你承诺会做的操作。因此如果你执行了真正的全局优化,const
的声明并不会对优化器产生任何的影响。
注意上文所说的,Example 3
中的大多数Z
声明为const
对编译器生成代码并不会有任何的优化。然而,编译器的优化器还是可能存在一些优化的。实际上,const
也可以有一些真正的优化的。
实际上存在程序性的优化,Z
可以智能的选择不同的方式来针对const
对象。作为例子,我们可以认为Z
是一个handle
类,例如String
会使用引用计数来完成懒复制。
// Example 4
//
void f( const String s )
{
// ...
s[4]; // or use iterators
// ...
}
类似于String
,对有const
修饰的String
执行operator[]()
时,不可以修改字符串的内容,这也许可以提供一个重载函数,该函数返回值而非引用。
class String
{
// ...
public:
char operator[]( size_t ) const;
char& operator[]( size_t );
// ...
}
类似于,String
也可以提供const_iterators
,调用operator*()
会返回值而非引用。
如果你使用operator[]()
或iterators
,并且传参时使用const
修饰,你会发现,String
会自动地帮助你避免深拷贝。
你可以通过简单的方法来获得上述效果。
// Example 5: Just do it -- better than Example 3
//
void f( const Z& z )
{
// ...
}
现在不管对象是不是一个handle
类或引用计数的东西,都可以实现避免深度拷贝的优化。这也是一个准则,使用const
引用的方式来传递参数,而不是简单的const
值传递。
这样可以帮助编译器生成更紧凑的代码。const
是一个好东西,但是更多的是对人类而言。当希望写出安全的代码的时候,const
可以强制编译器进行检测。在优化阶段,const
也是一个有用的工具,用于帮助类设计者实现手动优化,但是对编译器优化生成代码这方面却没有那么大的帮助。
1
wzzzx OP 之所以翻译这个是因为前阵子跟朋友讨论到 const 对于代码优化的影响,然后查看到了这个博客。所以今儿抽空做了一个翻译,有不好理解的地方欢迎指出~
|
2
codehz 2020-10-03 15:31:08 +08:00
原作者没有考虑 alias 分析的问题,虽然为了照顾程序员很多编译器是很保守的,但是有些编译器可以对 const 引用做更为激进的优化
https://software.intel.com/content/www/us/en/develop/documentation/cpp-compiler-developer-guide-and-reference/top/compiler-reference/compiler-options/compiler-option-details/advanced-optimization-options/alias-const-qalias-const.html?language=en |
4
12101111 2020-10-03 18:26:06 +08:00
建议标题上标上 C++, const 的语义在不同语言中是不一样的,同时具有 immutable 和 compile time constant 两种语义,编译器能做出的优化也是不一样的.immutable 相对而言能做的优化更少,但是可以有效的避免一些 bug.
alias 分析理论上可行但是工程上很难做到,rustc 在启用 no alias 时 LLVM 会编译出错误的结果,而 C/C++不靠程序员主动标记很难得到积极的结果. |