现代编译器缺省会使用RVO(return value optimization,返回值优化)、NRVO(named return value optimization、命名返回值优化)和复制省略(Copy elision)技术,来减少拷贝次数来提升代码的运行效率

注1:vc6、vs没有提供编译选项来关闭该优化,无论是debug还是release都会进行RVO和复制省略优化

注2:vc6、vs2005以下及vs2005+ Debug上不支持NRVO优化,vs2005+ Release支持NRVO优化

注3:g++支持这三种优化,并且可通过编译选项:-fno-elide-constructors来关闭优化

RVO

#include <stdio.h>
class A
{
public:
A()
{
printf("%p construct\n", this);
}
A(const A& cp)
{
printf("%p copy construct\n", this);
}
~A()
{
printf("%p destruct\n", this);
}
}; A GetA()
{
return A();
} int main()
{
{
A a = GetA();
} return ;
}

在g++和vc6、vs中,上述代码仅仅只会调用一次构造函数和析构函数 ,输出结果如下:

0x7ffe9d1edd0f construct
0x7ffe9d1edd0f destruct

在g++中,加上-fno-elide-constructors选项关闭优化后,输出结果如下:

0x7ffc46947d4f construct  // 在函数GetA中,调用无参构造函数A()构造出一个临时变量temp
0x7ffc46947d7f copy construct // 函数GetA return语句处,把临时变量temp做为参数传入并调用拷贝构造函数A(const A& cp)将返回值ret构造出来
0x7ffc46947d4f destruct // 函数GetA执行完return语句后,临时变量temp生命周期结束,调用其析构函数~A()
0x7ffc46947d7e copy construct // 函数GetA调用结束,返回上层main函数后,把返回值变量ret做为参数传入并调用拷贝构造函数A(const A& cp)将变量A a构造出来
0x7ffc46947d7f destruct // A a = GetA()语句结束后,返回值ret生命周期结束,调用其析构函数~A()
0x7ffc46947d7e destruct // A a要离开作用域,生命周期结束,调用其析构函数~A()

注:临时变量temp、返回值ret均为匿名变量

下面用c++代码模拟一下其优化行为:

#include <new>
A& GetA(void* p)
{
//由于p的内存是从外部传入的,函数返回后仍然有效,因此返回值可为A&
//vs中,以下代码还可以写成:
// A& o = *((A*)p);
// o.A::A();
// return o;
return *new (p) A(); // placement new
} int main()
{
{
char buf[sizeof(A)];
A& a = GetA(buf);
a.~A();
} return ;
}

NRVO

g++编译器、vs2005+ Release(开启/O2及以上优化开关)

修改上述代码,将GetA的实现修改成:

A GetA()
{
A o;
return o;
}

在g++、vs2005+ Release中,上述代码也仅仅只会调用一次构造函数和析构函数 ,输出结果如下:

0x7ffe9d1edd0f construct
0x7ffe9d1edd0f destruct

g++加上-fno-elide-constructors选项关闭优化后,和上述结果一样

0x7ffc46947d4f construct
0x7ffc46947d7f copy construct
0x7ffc46947d4f destruct
0x7ffc46947d7e copy construct
0x7ffc46947d7f destruct
0x7ffc46947d7e destruct

但在vc6、vs2005以下、vs2005+ Debug中,没有进行NRVO优化,输出结果为:

18fec4 construct  // 在函数GetA中,调用无参构造函数A()构造出一个临时变量o
18ff44 copy construct // 函数GetA return语句处,把临时变量o做为参数传入并调用拷贝构造函数A(const A& cp)将返回值ret构造出来
18fec4 destruct // 函数GetA执行完return语句后,临时变量o生命周期结束,调用其析构函数~A()
18ff44 destruct // A a要离开作用域,生命周期结束,调用其析构函数~A()

下面用c++代码模拟一下vc6、vs2005以下、vs2005+ Debug上的行为:

#include <new>
A& GetA(void* p)
{
A o;
//由于p的内存是从外部传入的,函数返回后仍然有效,因此返回值可为A&
//vs中,以下代码还可以写成:
// A& t = *((A*)p);
// t.A::A(o);
// return t;
return *new (p) A(o); // placement new
} int main()
{
{
char buf[sizeof(A)];
A& a = GetA(buf);
a.~A();
} return ;
}

注:与g++、vs2005+ Release相比,vc6、vs2005以下、vs2005+ Debug只优化掉了返回值到变量a的拷贝,命名局部变量o没有被优化掉,所以最后一共有2次构造和析构的调用

复制省略

典型情况是:调用构造函数进行值类型传参

void Func(A a)
{
} int main()
{
{
Func(A());
} return ;
}

在g++和vc6、vs中,上述代码仅仅只会调用一次构造函数和析构函数 ,输出结果如下:

0x7ffeb5148d0f construct
0x7ffeb5148d0f destruct

在g++中,加上-fno-elide-constructors选项关闭优化后,输出结果如下:

0x7ffc53c141ef construct   // 在main函数中,调用无参构造函数构造实参变量o
0x7ffc53c141ee copy construct // 调用Func函数后,将实参变量o做为参数传入并调用拷贝构造函数A(const A& cp)将形参变量a构造出来
0x7ffc53c141ee destruct // 函数Func执行完后,形参变量a生命周期结束,调用其析构函数~A()
0x7ffc53c141ef destruct // 返回main函数后,实参变量o要离开作用域,生命周期结束,调用其析构函数~A()

下面用c++代码模拟一下其优化行为:

void Func(const A& a)
{
} int main()
{
{
Func(A());
} return ;
}

优化失效的情况

开启g++优化,得到以下各种失效情况的输出结果:

(1)根据不同的条件分支,返回不同变量

A GetA(bool bflag)
{
A a1, a2;
if (bflag)
return a1;
return a2;
} int main()
{
A a = GetA(true); return ;
}
0x7ffc3cca324f construct
0x7ffc3cca324e construct
0x7ffc3cca327f copy construct
0x7ffc3cca324e destruct
0x7ffc3cca324f destruct
0x7ffc3cca327f destruct

注1:2次缺省构造函数调用:用于构造a1、a2

注2:1次拷贝构造函数调用:用于拷贝构造返回值

注3:这儿仍然用右值引用优化掉了一次拷贝函数调用:返回值赋值给a

(2)返回参数变量

(3)返回全局变量

(4)返回复合数据类型中的成员变量

(5)返回值赋值给已构造好的变量(此时会调用operator==赋值运算符)

参考

Return Value Optimization

What are copy elision and return value optimization?

Copy elision(wiki)

C++ 命名返回值优化(NRVO)

Named Return Value Optimization in Visual C++ 2005

C++编译器优化技术:RVO、NRVO和复制省略的更多相关文章

  1. 返回值优化(RVO)

    C++的函数中,如果返回值是一个对象,那么理论上它不可避免的会调用对象的构造函数和析构函数,从而导致一定的效率损耗.如下函数所示: A test() { A a; return a; } 在test函 ...

  2. 翻译「C++ Rvalue References Explained」C++右值引用详解 Part6:Move语义和编译器优化

    本文为第六部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/cpp-rvalue-references-explained-introduction.ht ...

  3. 【M20】协助完成“返回值优化(RVO)”

    1.方法返回对象,会导致临时对象的产生,这降低了效率,const Rational operator* (const Rational& lhs,Rational& rhs).有没有什 ...

  4. java编译期优化与执行期优化技术浅析

    java语言的"编译期"是一段不确定的过程.由于它可能指的是前端编译器把java文件转变成class字节码文件的过程,也可能指的是虚拟机后端执行期间编译器(JIT)把字节码转变成机 ...

  5. 【深入理解JAVA虚拟机】第4部分.程序编译与代码优化.2.运行期优化。这章提到的具体的优化技术,应该对以后做性能工作会有帮助。

    1.概述 Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”(Hot Spot Code). 为了提高 ...

  6. 《深入理解java虚拟机》学习笔记之编译优化技术

    郑重声明:本片博客是学习<深入理解Java虚拟机>一书所记录的笔记,内容基本为书中知识. Java程序员有一个共识,以编译方式执行本地代码比解释方式更快,之所以有这样的共识,除去虚拟机解释 ...

  7. JDK and JRE File Structure JAVA_HOME HotSpot优化技术

    https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jdkfiles.html Java Platform, Standard ...

  8. java编译器优化和运行期优化

    概述    最近在看jvm优化,总结一下学习的相关知识 (一)javac编译器 编译过程 1.解析与填充符号表过程 1).词法.语法分析    词法分析将源代码的字符流转变为标记集合,单个字符是程序编 ...

  9. node.js背后的引擎V8及优化技术

    本文将挖掘V8引擎在其它方面的代码优化,如何写出高性能的代码,及V8的性能诊断工具.V8是chrome背后的javascript引擎,因此本文的相关优化经验也适用于基于chrome浏览器的javasc ...

随机推荐

  1. final关键字、finally代码块和finalize()方法有什么区别?

    1. final是关键字,final可以修饰类.方法.属性. 如果一个类被final修饰,那么这个类就是最终类,不能派生出新的子类,不能作为父类被继承,该类中的所有方法都不能被重写,但是final类中 ...

  2. 《Dotnet9》系列-开源C# WPF项目强力推荐

    时间如流水,只能流去不流回! 点赞再看,养成习惯,这是您给我创作的动力! 本文 Dotnet9 https://dotnet9.com 已收录,站长乐于分享dotnet相关技术,比如Winform.W ...

  3. ubuntu14.04编译vim8.1

    安装依赖 这一步其实我没做,直接下载编译成功了.估计有些包不是必需的.姑且列在这里供参考 sudo apt install libncurses5-dev libgnome2-dev libgnome ...

  4. Internet History,Technology,and Security - Technology: Internets and Packets (Week5)

    Week5 Technology: Internets and Packets Welcome to Week 5! This week, we’ll be covering internets an ...

  5. 如何在云服务器上自动运行.py文件

    如果你在云服务器上运行的目的是保持一直运行,那就继续往下看吧. 有很多种方法,我这里说的是在linux上操作的一种. 利用screen会话分离. 因为在Screen环境下,所有的会话都独立的运行,并拥 ...

  6. DDMS files not found: xxx\hprof-conv.exe

    出现如下错误: DDMS files not found: xxx\hprof-conv.exe The connection to adb is down, and a severe error h ...

  7. Cortex-A7处理器算数运算指令和逻辑运算指令

      汇编中也可以进行算术运算, 比如加减乘除,常用的运算指令用法如表所示: 常用运算指令 在嵌入式开发中最常会用的就是加减指令,乘除基本用不到. 我们用 C 语言进行CPU 寄存器配置的时候常常需要用 ...

  8. ReactNative: 使用AppReistry注册类

    一.简介 每一个应用程序的运行都有一个入口文件或者入口函数,例如iOS中的使用UIApplicationMain类完成入口函数的实现,在React-Native中,AppRegistry类就肩负着这个 ...

  9. 事隔五年之后,开启第2版DSP数字信号处理和CMSIS-NN神经网络教程,同步开启三代示波器,前15章发布(2019-11-04)

    说明:1.第1版DSP教程发布于2014年末,纪念下:https://www.cnblogs.com/armfly/p/11274826.html2.这几年在信号处理的应用上积累了一些经验,也发现了很 ...

  10. 一文带你深入了解JVM性能调优以及对JVM调优的全面总结

    目录 JVM调优 概念 基本垃圾回收算法 垃圾回收面临的问题 分代垃圾回收详述1 分代垃圾回收详述2 典型配置举例1 典型配置举例2 新一代的垃圾回收算法 调优方法 反思 一.JVM调优的一些概念 数 ...