美文网首页
C++对象模型及性能优化杂谈二

C++对象模型及性能优化杂谈二

作者: fooboo | 来源:发表于2018-12-01 19:03 被阅读24次

    这篇主要介绍返回值优化及移动构造(赋值)函数,后者是C++11中的知识点。本来是不准备写的,因为这些都比较基础且编译器为我们进行了一定的优化,但还是想记录下代码层面所发生的事情,且为最近相关C++11新知识点的学习进行简单总结。

    在进行下面之前,需要了解什么是构造函数,默认构造函数,拷贝构造函数,赋值运算符,析构函数,以及什么情况下编译器会为类合成在编译器看来有用的构造(和其他几个)函数,这些知识可以参考《深度探索C++对象模型》和《(More)Effective C++》。

    以最基本的类开始分析:

      5 class CBase {
      6 public:
      7     CBase(const uint32_t m = 0) : m_count(m) {
      8         std::cout << "CBase ctor" << std::endl;
      9     }
     10 
     11     CBase(const CBase& lhs) {
     12         std::cout << "CBase copy ctor" << std::endl;
     13     }
     14 
     15     ~CBase() {
     16         std::cout << "CBase dtor" << std::endl;
     17     }
     18 private:
     19     uint32_t m_count;
     20 };
     21 
     22 CBase GetObjWithNoMove() {
     23     return CBase();
     24 }
     25 
     26 int main(int argc, char** argv) {
     27     CBase obj = GetObjWithNoMove();
     28     return 0;
     29 }
    

    版本为7.3.0,直接g++ 编译,不加任何选项,输出为:

    CBase ctor
    CBase dtor
    

    不考虑析构的代码,其他反汇编如下:

    138 __Z16GetObjWithNoMovev:
    139 100000d34:  55  pushq   %rbp
    140 100000d35:  48 89 e5    movq    %rsp, %rbp
    141 100000d38:  48 83 ec 10     subq    $16, %rsp
    142 100000d3c:  48 89 7d f8     movq    %rdi, -8(%rbp)
    143 100000d40:  48 8b 45 f8     movq    -8(%rbp), %rax
    144 100000d44:  be 00 00 00 00  movl    $0, %esi
    145 100000d49:  48 89 c7    movq    %rax, %rdi
    146 100000d4c:  e8 9b 00 00 00  callq   155
    
    151 _main:
    152 100000d57:  55  pushq   %rbp
    153 100000d58:  48 89 e5    movq    %rsp, %rbp
    154 100000d5b:  53  pushq   %rbx
    155 100000d5c:  48 83 ec 28     subq    $40, %rsp
    156 100000d60:  89 7d dc    movl    %edi, -36(%rbp)
    157 100000d63:  48 89 75 d0     movq    %rsi, -48(%rbp)
    158 100000d67:  48 8d 45 ec     leaq    -20(%rbp), %rax
    159 100000d6b:  48 89 c7    movq    %rax, %rdi
    160 100000d6e:  e8 c1 ff ff ff  callq   -63 <__Z16GetObjWithNoMovev>
    

    结合源码分析,汇编从152〜159似乎是在准备什么参数,然后调用GetObjWithNoMove,但是从源码并没有传实参,但这里为何会这样呢?就debug看一下这几行代码是什么玩意。
    启动的时候,就直接定位在=> 0x0000000100000d67 <+16>: lea -0x14(%rbp),%rax把地址mov到rax中,查看下obj地址和rbp-0x20的值为:

    (gdb) p obj
    $4 = {m_count = 0}
    (gdb) p &obj
    $5 = (CBase *) 0x7ffeefbffa8c
    (gdb) i r rbp
    rbp            0x7ffeefbffaa0   0x7ffeefbffaa0
    (gdb) x/w 0x7ffeefbffa8c
    0x7ffeefbffa8c: 0x00000000
    

    我们new一个对象的时候,先是分配一段足够的空间,再调用构造函数,再返回地址,当然后两步可以顺序对调,这在多线程中会有些问题,不讨论。
    这里先分配一段空间,然后作为参数调用函数GetObjWithNoMove,再里面进行了构造函数的调用:

       0x0000000100000d3c <+8>: mov    %rdi,-0x8(%rbp)
    => 0x0000000100000d40 <+12>:    mov    -0x8(%rbp),%rax
       0x0000000100000d44 <+16>:    mov    $0x0,%esi
       0x0000000100000d49 <+21>:    mov    %rax,%rdi
       0x0000000100000d4c <+24>:    callq  0x100000dec
    
    
    (gdb) si
    CBase::CBase (this=0x100000000, m=32766) at move.cpp:7
    7       CBase(const uint32_t m = 0) : m_count(m) {
    (gdb) disassemble 
    Dump of assembler code for function CBase::CBase(unsigned int):
    => 0x0000000100000cb4 <+0>: push   %rbp
       0x0000000100000cb5 <+1>: mov    %rsp,%rbp
       0x0000000100000cb8 <+4>: sub    $0x10,%rsp
       0x0000000100000cbc <+8>: mov    %rdi,-0x8(%rbp)
       0x0000000100000cc0 <+12>:    mov    %esi,-0xc(%rbp)
       0x0000000100000cc3 <+15>:    mov    -0x8(%rbp),%rax
       0x0000000100000cc7 <+19>:    mov    -0xc(%rbp),%edx
       0x0000000100000cca <+22>:    mov    %edx,(%rax)
    

    是编译器为我们进行了优化?那原来的过程是怎么样的呢?重新编译并加选项-fno-elide-constructors并重新执行结果如下:

    CBase ctor
    CBase copy ctor
    CBase dtor
    CBase copy ctor
    CBase dtor
    CBase dtor
    

    这里解释一下,然后再进行反汇编说明。在调用GetObjWithNoMove过程中,先进行了一次构造CBase(),此时打印第一行,然后调用拷贝构造作为返回值,打印第二行,执行完此条语句后,便进行析构,即打印第三行。GetObjWithNoMove结束返回一个临时对象,并以拷贝构造obj,即第四行,执行完后析构,最后析构obj。在这个过程中生成了两个临时对象,真正有用的是最后一次,这里没有涉及到复杂的对象构造和深拷贝。
    以下是部分汇编代码,进行了旁注:

    Dump of assembler code for function main(int, char**):
       0x0000000100000cb5 <+0>: push   %rbp
       0x0000000100000cb6 <+1>: mov    %rsp,%rbp
       0x0000000100000cb9 <+4>: push   %rbx
       0x0000000100000cba <+5>: sub    $0x28,%rsp
       0x0000000100000cbe <+9>: mov    %edi,-0x24(%rbp)
       0x0000000100000cc1 <+12>:    mov    %rsi,-0x30(%rbp)
       0x0000000100000cc5 <+16>:    lea    -0x14(%rbp),%rax
    => 0x0000000100000cc9 <+20>:    mov    %rax,%rdi
       0x0000000100000ccc <+23>:    callq  0x100000c50 <GetObjWithNoMove()>
       0x0000000100000cd1 <+28>:    lea    -0x14(%rbp),%rdx
       0x0000000100000cd5 <+32>:    lea    -0x18(%rbp),%rax
       0x0000000100000cd9 <+36>:    mov    %rdx,%rsi
       0x0000000100000cdc <+39>:    mov    %rax,%rdi
       0x0000000100000cdf <+42>:    callq  0x100000d8a  //拷贝构造
       0x0000000100000ce4 <+47>:    lea    -0x14(%rbp),%rax
       0x0000000100000ce8 <+51>:    mov    %rax,%rdi
       0x0000000100000ceb <+54>:    callq  0x100000d96  //析构函数
       0x0000000100000cf0 <+59>:    mov    $0x0,%ebx
       0x0000000100000cf5 <+64>:    lea    -0x18(%rbp),%rax
       0x0000000100000cf9 <+68>:    mov    %rax,%rdi
       0x0000000100000cfc <+71>:    callq  0x100000d96  //析构函数
    
    Dump of assembler code for function GetObjWithNoMove():
    => 0x0000000100000c50 <+0>: push   %rbp
       0x0000000100000c51 <+1>: mov    %rsp,%rbp
       0x0000000100000c54 <+4>: push   %rbx
       0x0000000100000c55 <+5>: sub    $0x28,%rsp
       0x0000000100000c59 <+9>: mov    %rdi,-0x28(%rbp)
       0x0000000100000c5d <+13>:    lea    -0x14(%rbp),%rax
       0x0000000100000c61 <+17>:    mov    $0x0,%esi
       0x0000000100000c66 <+22>:    mov    %rax,%rdi
       0x0000000100000c69 <+25>:    callq  0x100000d90 //构造函数
       0x0000000100000c6e <+30>:    lea    -0x14(%rbp),%rdx
       0x0000000100000c72 <+34>:    mov    -0x28(%rbp),%rax
       0x0000000100000c76 <+38>:    mov    %rdx,%rsi
       0x0000000100000c79 <+41>:    mov    %rax,%rdi
       0x0000000100000c7c <+44>:    callq  0x100000d8a  //拷贝构造
       0x0000000100000c81 <+49>:    nop
       0x0000000100000c82 <+50>:    lea    -0x14(%rbp),%rax
       0x0000000100000c86 <+54>:    mov    %rax,%rdi
       0x0000000100000c89 <+57>:    callq  0x100000d96 //析构函数
    
    

    为了减少其中的构造成本,以下是编译器为这种代码优化的实现伪代码,即返回值优化,这个函数会被编译器转化为GetObjWithNoMove

    void GetObjWithNoMove(CBase& obj) {
        obj.CBase::CBase();
        //do something...
    }
    CBase obj;
    GetObjWithNoMove(obj);
    

    如果原来是这样的形式:

    CBase GetObjWithNoMove() {
        CBase tmp;
        //do something...
        return tmp;
    }
    

    还是会被优化成上面的形式,不过网上和书上有提及那种NRVO优化,用拷贝构造的形式,可能跟写法有关系:

    void GetObjWithNoMove(CBase& obj) {
        tmp.CBase::CBase();
        obj.CBase::CBase(tmp); //copy ctor
        tmp.CBase::~CBase();
        return;
    }
    

    不过一般不会像上面这么写代码,再说编译器也为我们进行了优化。
    具体可参考《深度探索C++对象模型》第二章的构造函数语意学。
    不过这书比较老,最新版的C++11加了许多新的特性,比如移动构造,移动赋值。这些不是很难理解,我个人在工作中用C++11的机会不多,平时都只是从网上和书上,开源项目中学习到的使用方法。

    以上涉及到万能引用,右值引用,和完美转发,这些可以看参考资料。

    包括最新的emplace_back之类的接口直接在内存地址上构造而不是使用push_back之类的接口,后者可能生成临时对象等性能方面的原因。

    当然使用emplace之类的需要自定义类带有构造函数,不然会报如下的错误:

    /usr/local/Cellar/gcc@7/7.3.0/include/c++/7.3.0/ext/new_allocator.h:136:4: error: no matching function for call to 'CString::CString(int)'
      { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
    move.cpp:16:5: note: candidate: CString::CString(const CString&)
         CString(const CString& str) {
    move.cpp:16:5: note:   no known conversion for argument 1 from 'int' to 'const CString&'
    

    还有些情况下不能使用emplace之类的,具体原因可能要在实践中去发现,比如参考中的会涉及,但是看的云里雾里的。
    emplace_back部分源码如下:

    template<class _Objty,
            class... _Types>
            static void construct(_Alloc&, _Objty * const _Ptr, _Types&&... _Args)
            {   // construct _Objty(_Types...) at _Ptr
            ::new (const_cast<void *>(static_cast<const volatile void *>(_Ptr)))
                _Objty(_STD forward<_Types>(_Args)...);
            }
    

    还是要多学习,和运用。
    这篇没啥干货,后面会备忘C++11中的一些实现源码。

    参考资料:
    《深度探索C++对象模型》
    《Effective Modern C++》
    C++雾中风景9:emplace_back与可变长模板

    相关文章

      网友评论

          本文标题:C++对象模型及性能优化杂谈二

          本文链接:https://www.haomeiwen.com/subject/xohixqtx.html