V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
wzzzx
V2EX  ›  程序员

[翻译] const 是否会对程序的优化产生影响

  •  1
     
  •   wzzzx · 2020-10-03 15:24:05 +08:00 · 2405 次点击
    这是一个创建于 1514 天前的主题,其中的信息可能已经有所发展或是发生改变。

    来源:http://www.gotw.ca/gotw/081.htm

    1. 函数参数声明为const和返回值声明为const,是否会对编译器优化代码产生影响?

      // Example 1
      //
      const Y& f( const X& x )
      {
        // ... do something with x and find a Y object ...
        return someY;
      }
    

    简要地说,也许并不会。 编译器能做的更好的是什么?它能够避免传参和返回值的拷贝开销吗?并不,因为参数和返回值是通过引用来进行传递的。它能够将xsomeY拷贝到只读内存区域吗?也不会。对于xsomeY而言,它们的生命周期均在该函数的作用域之外。即便someY是在函数内部动态创建的,它的所有权也会被传递给调用者。 但在f()的内部,有没有可能会对代码进行一定的优化呢?因为const的存在,会不会在某种程度上导致编译器生成更加优化的代码呢?

    2. 从一般化的情况来看,使用与否const为什么会对编译器的代码优化产生影响

    回到Example 1,使用const修饰参数不会导致编译器对代码产生额外优化的原因是,即便调用一个const类成员函数,编译器也没法认定xsomeY的值不会发生改变。再者,除非编译器进行全局优化,不然会有其他的一些问题存在:首先,编译器并不知道其他的代码会不会持有对象xsomeY的非const引用,这些变量可能会在f()执行期间被意外地使用。其次,编译器也不一定会知道,xxomeY所引用的对象,是否是被主动声明为一个const变量。 仅仅因为xy被声明为const常量,也不一定意味它们物理上的位是常量。因为这些类可能会存在可变成员,或者其他等价的东西,即在成员函数内部进行const_cast强制转换或者C语言样式的强制转换,导致被声明为const常量并不是一个真正的常量。 只有在变量定义的时候,声明为const常量,此时说const常量才具有意义。特别是这些变量是能够在编译期间就生成的POD类型时,编译器可以将这些真正的const常量对象放置到只读内存中,同时会被存储到可执行文件本身。这样的对象会被称为ROM-able

    3. 依下列代码考虑三个小问题:

      // Example 3
      //
      void f( const Z z )
      {
        // ...
      }
    

    有三个小问题需要思考:

    1. 什么环境下,某些特定类型的Z是否可以使编译器生成不一样且更好的代码?
    2. 在问题 a 所提及的情况下, 我们是在讨论编译器的优化还是其他方面的优化。
    3. 有更好的取得相同的效果吗?

    3.1 什么环境下,某些特定类型的Z是否可以使编译器生成不一样且更好的代码?

    简要回答就是:能,也不能。 先说一下能的部分。因为编译器知道z是一个真正的常量对象,所以可以在没有进行全局优化的情况下,进行一些有用的优化。例如,如果在f()内有一个这样的调用:g(&z)。编译器可以明确地知道在调用g()的时候,z不会发生变化。 除此之外,在参数值声明为const常量,对于大多数的z而言,并不会产生任何形式的优化。在这种情况下的优化,并不是编译器代码生成优化。 对于编译器代码生成优化,问题主要归结为编译器是否可以省略拷贝构造,或者是否可以将z放入只读内存中。这意味着,对象zf()函数内部不会受到任何的影响,所以我们可以直接使用函数外部的对象而不需要多一次的拷贝赋值操作。或者即便需要拷贝,也可以直接将拷贝后的对象直接放置在只读内存中,用到的时候直接读取。 一般情况下,编译器不会因为参数是const的就主动消除拷贝构造函数。如同上文所提到的一样,这可能会导致太多的问题。尤其是Z拥有可变成员的时候,或者在其他地方直接或间接使用到Z的成员函数时,可能会使用const_casts来改变常量。 但有一些办法来帮助编译器生成更好的代码,如下几点:

    • 对于Z的拷贝构造函数和所有在f()中直接或间接用到的成员函数都是可见的。
    • 这些函数都非常简单且没有任何副作用
    • 编译器有较为激进的优化策略

    满足上述三点会让编译器明确每一步具体发生了什么,所以编译器能够在as-if规则的限制下,自主选择消除一些不必要的代码。 另外,还有一点值得一提。有些人会说编译器执行全局优化的时候,可以对const常量提供优化以生成更好的代码。事实上,即便去掉const修饰,也可以对生成代码进行优化。不要介意全局优化的频次低且代价高。这里真正的问题是全局优化清楚所有对象的真正使用情况,所以不管有无声明为const,都会执行相同的优化操作。全局优化针对的是你真正的操作,而不是你承诺会做的操作。因此如果你执行了真正的全局优化,const的声明并不会对优化器产生任何的影响。 注意上文所说的,Example 3中的大多数Z声明为const对编译器生成代码并不会有任何的优化。然而,编译器的优化器还是可能存在一些优化的。实际上,const也可以有一些真正的优化的。

    在问题 3.1 所提及的情况下, 我们是在讨论编译器的优化还是其他方面的优化。

    实际上存在程序性的优化,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会自动地帮助你避免深拷贝。

    3.3 有更好的取得相同的效果吗?

    你可以通过简单的方法来获得上述效果。

      // Example 5: Just do it -- better than Example 3
      //
      void f( const Z& z )
      {
        // ...
      }
    

    现在不管对象是不是一个handle类或引用计数的东西,都可以实现避免深度拷贝的优化。这也是一个准则,使用const引用的方式来传递参数,而不是简单的const值传递。 这样可以帮助编译器生成更紧凑的代码。const是一个好东西,但是更多的是对人类而言。当希望写出安全的代码的时候,const可以强制编译器进行检测。在优化阶段,const也是一个有用的工具,用于帮助类设计者实现手动优化,但是对编译器优化生成代码这方面却没有那么大的帮助。

    5 条回复    2020-10-05 01:17:50 +08:00
    wzzzx
        1
    wzzzx  
    OP
       2020-10-03 15:29:42 +08:00
    之所以翻译这个是因为前阵子跟朋友讨论到 const 对于代码优化的影响,然后查看到了这个博客。所以今儿抽空做了一个翻译,有不好理解的地方欢迎指出~
    codehz
        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
    wzzzx
        3
    wzzzx  
    OP
       2020-10-03 16:29:27 +08:00
    @codehz #2 alias 分析还是第一次听说,刚刚看了下相关的东西,发现这又是一个大问题了。
    12101111
        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++不靠程序员主动标记很难得到积极的结果.
    wzzzx
        5
    wzzzx  
    OP
       2020-10-05 01:17:50 +08:00
    @12101111 #4 学习了。我现在去了解一下关于 immutable 和 compile time constant 的知识。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1106 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 19:04 · PVG 03:04 · LAX 11:04 · JFK 14:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.