详解C++编译器优化技术

前言

注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 0;
}

在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 0;
}

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 0;
}

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

复制省略

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

void Func(A a)
{
}

int main()
{
    {
        Func(A());
    }

    return 0;
}

在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 0;
}

优化失效的情况

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

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

A GetA(bool bflag)
{
    A a1, a2;
    if (bflag)
        return a1;
    return a2;
}

int main()
{
    A a = GetA(true);

    return 0;
}

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==赋值运算符)

以上就是详解C++编译器优化技术的详细内容,更多关于C++编译器优化技术的资料请关注我们其它相关文章!

(0)

相关推荐

  • C++小知识:不要去做编译器的工作

    对于C++编程的老鸟来说,有时候他们喜欢把一些东西按照编译器的工作原理进行改写,以便提高代码的运行效率.这么做确实高明,也能体现出程序员的水平,但是这么做也是有风险的.因为有时候你可能会因为一些简单的笔误,而造成非常难以察觉的错误.本文就给出了类似的例子. 这个Bug 出现在MySQL源代码中. 错误代码: static int rr_cmp(uchar *a,uchar *b) { if (a[0] != b[0]) return (int) a[0] - (int) b[0]; if (a[

  • C++ 中lambda表达式的编译器实现原理

    什么是Lambda? C++ 11加入了一个非常重要的特性--Lambda表达式.营里(戴维营)的兄弟都对Objective-C很熟悉,许多人多block情有独钟,将各种回调函数.代理通通都用它来实现.甚至有人选择用FBKVOController.BlocksKit等开源框架将KVO.控件事件处理都改为通过block解决.原因就是简单.方便.直观,函数的定义和使用出现在同一个地方.这里的Lambda表达式实际上和block非常类似,当然如果你用它和Swift语言的闭包比较,那就是一回事了. 现在

  • C/C++ 编译器优化介绍

    0. gcc -o gcc -o 的优化仍然是机械的,想当然的.只有做到深入理解计算机系统,加深对编程语言的理解,才能写出最优化的代码. Linux下gcc 优化级别的介绍  · gcc -o0 ⇒ 不提供任何优化:  · gcc -o1 ⇒ 最基本的优化,主要对代码的分支.表达式.常量等进行优化,编译器会在较短的时间下将代码变得更加短小,这样体积就会变得更小,会减少内存的占用率,在操作系统进行内存调度时就会更快.          · 但是事情没有绝对的优点,当一个庞大的程序被拆碎细分的话,内

  • 总结一次C++ 程序优化历程

    近期用到了一位师兄写的C++程序,总体功能良好.使用不同的数据测试,发现了一个明显的缺点:大数据量下,预处理过程耗时很长.中科院的某计算集群,普通队列中的程序运行时间不能超过6个小时.而手上这套程序,大数据量下预处理就花了不止六个小时,结果当然是还没开始就被结束了. 和天河二号的工作人员联系,确认没有执行时间限制.于是开通了天河二号的账号,把程序扔上去跑.执行大数据量时,程序莫名被kill.询问技术支持,得知是内存耗尽,建议每个节点的进程数少一点.如此折腾了两次,大数据量的例子没跑通,大部分时间

  • C++ cin速度优化详解

    如下所示: std::ios::sync_with_stdio(false); 很多C++的初学者可能会被这个问题困扰,经常出现程序无故超时,最终发现问题处在cin和cout上,(甚至有些老oier也会被这个问题困扰,每次只能打scanf和printf,然后一堆的占位符巨麻烦),这是因为C++中,cin和cout要与stdio同步,中间会有一个缓冲,所以导致cin,cout语句输入输出缓慢,这时就可以用这个语句,取消cin,cout与stdio的同步,说白了就是提速,效率基本与scanf和pri

  • c++并查集优化(基于size和rank)

    基于size的优化是指:当我们在指定由谁连接谁的时候,size数组维护的是当前集合中元素的个数,让数据少的指向数据多的集合中 基于rank的优化是指:当我们在指定由谁连接谁的时候,rank数组维护的是当前集合中树的高度,让高度低的集合指向高度高的集合 运行时间是差不多的: 基于size的代码: UnionFind3.h #ifndef UNION_FIND3_H_ #define UNION_FIND3_H_ #include<iostream> #include<cassert>

  • C++17 使用 std::string_view避免字符串拷贝优化程序性能

    C++中std::string是日常Coding中经常使用的一个类,使用起来非常方便,但是也存在一些弊端. 如下代码,参数传递的过程发生了内存分配(Memory Allocation)和内存拷贝. void fun(const std::string& s) { std::cout << s << std::endl; } const char* ch = "hello world"; // bad way, expensive if the strin

  • C/C++编译器GCC下的常用编译命令总结

    简介 GCC的意思是GUN C Compiler.经过了多年的发展,GCC已经不仅仅能支持C语言了,它现在还支持Ada语言.C++语言.Java语言.Objective C语言.Pascal语言.COBOL语言,以及支持函数式编程和逻辑编程的Mercury语言等等.而GCC也不再单只是GUN C语言编译器的意思了,而是变成了GUN Complier Collection也即是GUN编译器家族的意思了.另一方面,说到GCC对于操作系统平台以及硬件平台支持,概括起来就一句话:无所不在. 编译 示例程

  • C++编译器Clion的使用详解(总结)

    推荐教程 Clion2020.2.x最新激活版附安装教程(Mac Linux Windows) 最新clion2020激活附安装教程(亲测有效) Clion是由JetBrains公司开发的一款跨平台的C++ IDE,风格接近Qt creator和Xcode,Clion特点是可以智能的进行代码重构,比如改名,提取类,提取函数等,Clion与Java IDE IntelliJ IDEA功能上很接近,本人从CLion 1.0就开始关注,但当时初级版本确实还不适合在较大的项目上使用,另外一方面Clion

  • 详解C++编译器优化技术

    前言 注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\

  • 详解python字符串驻留技术

    前言 每种编程语言为了表现出色,并且实现卓越的性能,都需要有大量编译器级与解释器级的优化. 由于字符串是任何编程语言中不可或缺的一个部分,因此,如果有快速操作字符串的能力,就可以迅速地提高整体的性能. 在本文中,我们将深入研究 Python 的内部实现,并了解 Python 如何使用一种名为字符串驻留(String Interning)的技术,实现解释器的高性能.本文的目的不仅在于介绍 Python 的内部知识,而且还旨在使读者能够轻松地浏览 Python 的源代码:因此,本文中将有很多出自CP

  • 详解Android性能优化之启动优化

    1.为什么要进行启动优化 网上流行一种说法,就是8秒定律,意思是说,如果用户在打开一个页面,在8秒的时间内还没有打开,那么用户大概的会放弃掉,意味着一个用户的流失.从这里就可以看出,启动优化的重要性了. 2.启动的分类 2.1 冷启动 先来看看冷启动的流程图 从图中可以看出,APP启动的过程是:ActivityManagerProxy 通过IPC来调用AMS(ActivityManagerService),AMS通过IPC启动一个APP进程,ApplicationThread通过反射来创建App

  • 详解Mysql函数调用优化

    函数调用优化 MySQL函数在内部被标记为确定性或不确定性.如果给定参数固定值的函数可以为不同的调用返回不同的结果,则它是不确定的.不确定函数的示例: RAND(), UUID(). 如果某个函数被标记为不确定的,则将WHERE针对每一行(从一个表中选择时)或行的组合(从多表联接中选择时)评估子句中对该函数的引用. MySQL还根据参数的类型(参数是表列还是常量值)确定何时评估函数.每当表列更改值时,都必须评估将表列作为参数的确定性函数. 非确定性函数可能会影响查询性能.例如,某些优化可能不可用

  • 详解python 内存优化

    写在之前 围绕类的话题,说是说不完的,仅在特殊方法,除了我们在前面遇到过的 __init__(),__new__(),__str__() 等之外还有很多.虽然它们只是在某些特殊的场景中才会用到,但是学会它们却可以成为你熟悉这门语言路上的铺路石. 所以我会在试图介绍一些「黑魔法」,让大家多多感受一下 Python 的魅力所在,俗话说「艺多不压身」就是这个道理了. 内存优化 首先先让我们从复习前面的类属性和实例属性的知识来引出另一个特殊方法: >>> class Sample: ... na

  • 详解阿里Node.js技术文档之process模块学习指南

    模块概览 process是node的全局模块,作用比较直观.可以通过它来获得node进程相关的信息,比如运行node程序时的命令行参数.或者设置进程相关信息,比如设置环境变量. 环境变量:process.env 使用频率很高,node服务运行时,时常会判断当前服务运行的环境,如下所示 if(process.env.NODE_ENV === 'production'){ console.log('生产环境'); }else{ console.log('非生产环境'); } 运行命令 NODE_EN

  • 详解Java编译优化之循环展开和粗化锁

    循环展开和粗化锁 我们先来回顾一下什么是循环展开. 循环展开就是说,像下面的循环遍历的例子: for (int i = 0; i < 1000; i++) { x += 0x51; } 因为每次循环都需要做跳转操作,所以为了提升效率,上面的代码其实可以被优化为下面的: for (int i = 0; i < 250; i++) { x += 0x144; //0x51 * 4 } 注意上面我们使用的是16进制数字,至于为什么要使用16进制呢?这是为了方便我们在后面的assembly代码中快速找

  • 详解commons-pool2池化技术

    目录 一.前言 二.commons-pool2池化技术剖析 2.1.核心三元素 2.1.1.ObjectPool 2.1.2.PooledObjectFactory 2.1.3.PooledObject 2.2.对象池逻辑分析 2.2.1.对象池接口说明 2.2.2.对象创建解耦 2.2.3.对象池源码分析 2.3.核心业务流程 2.3.1.池化对象状态变更 2.3.2.对象池browObject过程 2.3.3.对象池returnObject的过程执行逻辑 2.4.拓展和思考 2.4.1.关于

  • 详解Android内存优化策略

    目录 前言 一.内存优化策略 二.具体优化的点 1.避免内存泄漏 2.Bitmap等大对象的优化策略 (1) 优化Bitmap分辨率 (2) 优化单个像素点内存 (3) Bitmap的缓存策略 (4) drawable资源选择合适的drawable文件夹存放 (5) 其他大对象的优化 (6) 避免内存抖动 3.原生API回调释放内存 4.内存排查工具 (1)LeakCanary监测内存泄漏 (2)通过Proflier监控内存 (3)通过MAT工具排查内存泄漏 总结 前言 在开始之前需要先搞明白一

  • 详解webpack性能优化——DLL

    Webpack性能优化的方式有很多种,本文之所以将 dll 单独讲解,是因为 dll 是一种最简单粗暴并且极其有效的优化方式. 在通常的打包过程中,你所引用的诸如:jquery.bootstrap.react.react-router.redux.antd.vue.vue-router.vuex 等等众多库也会被打包进 bundle 文件中.由于这些库的内容基本不会发生改变,每次打包加入它们无疑是一种巨大的性能浪费. Dll 的技术就是在第一次时将所有引入的库打包成一个 dll.js 的文件,将

随机推荐