C++性能优化实践 二

C++性能优化实践 二

文章目录

        • 一、返回对象
          • 1.1、返回值优化
        • 二、异常之得失
          • 2.1、避免不必要的 try...catch
        • 三、错误码机制
          • 3.1、集成错误码一 标识错误码
          • 3.2、集成错误码二 错误类别和输出
          • 3.3、集成错误码三 错误转换成标准库错误码
          • 3.4、集成错误码四 构造 error_code

 书接上回, 这篇文章继续来谈谈C++性能优化相关的内容。参考文章: https://boolan.com/

一、返回对象

  在函数输出变量值时尽量使用返回值的方式输出而非输出参数, 优点是代码直观方便阅读而且性能高, 没有不必要的拷贝发生。某些重载的运算符可以在一行里面写出来,比如:

std::string A, B, C;
std::string D = A+B+C;        
1.1、返回值优化

 考虑如下代码:

struct A
{
    A(){std::cout<<"create obj"<<std::endl;}
    ~A(){std::cout<<"destory obj"<<std::endl;}
    A(const A& that){std::cout<<"copy create obj"<<std::endl;}
    A(A&& that){std::cout<<"move create obj"<<std::endl;}    
};
A GetObj()
{
    A a;
    return a;
}

int main()
{
    auto a = GetObj();
    return 0;
}       

此时输出为 “create obj” “destory obj”。我在刚毕业工作的时候以为是有拷贝构造要发生的, 当时还不晓得有返回值优化这个东东。直观上讲就是编译器在编译的时候直接就知道该在何处构造。但是当它无法确定的时候就会发生移动构造了, 考虑如下代码:

struct A
{
    A(){std::cout<<"create obj"<<std::endl;}
    ~A(){std::cout<<"destory obj"<<std::endl;}
    A(const A& that){std::cout<<"copy create obj"<<std::endl;}
    A(A&& that){std::cout<<"move create obj"<<std::endl;}    
};

A GetObjMove()
{
    A a1;
    A a2;
    if (rand()> 100){
        return a1;
    }else{
        return a2;
    }
}

int main()
{
    auto a = GetObjMove();
    return 0;
}       

此时输出为 “create obj” “create obj” “move create obj” destory obj" destory obj" destory obj" 了。
所以通常情况下直接返回对象。有些例外情况不该返回值类型比如:
  ①非值类型, 比如派生类对象, 这个时候就借助 std::unique_ptr 或者 std::shared_ptr来返回。
  ②移动的开销很大, 考虑将其分配在堆上, 借助 std::unique_ptr 来返回。或者传递非const 的目标引用形参来修改。
  ③函数内会重用一个带容量的对象, 这个时候也建议使用非const 的目标引用形参来修改。

二、异常之得失

 使用C++异常机制它的优点是可以集中处理一些代码错误, 在不使用异常的时候写代码假如要返回错误类型,通常需要先在头文件里面用宏或者枚举定义各种错误值, 当出现异常的时候再一层一层的返回, 这期间通常有较多的 if_else。而且有时会因为疏忽导致某些错误类型被忽视, 在调试的时候假如没有发现, 就相当于代码埋雷了。所以用了异常机制后, 代码会更简洁, 易读, 可调式。

2.1、避免不必要的 try…catch

  在实现智能指针的时候可能会出现如下代码:

    if (ptr){
        try{
            m_shared_cnt = new SharedCount();
        }
        catch(const std::bad_alloc& e){
            delete ptr;
            std::throw;
        }        
    }

假如引用计数模块内存申请失败了, 则可以需要把用户传进来的原生指针释放掉, 否则会内存泄漏。这个逻辑没有问题, 但是这里可以用更好的方式来保证避免内促泄漏, 比如C++最重要的特性RAII。

    if (ptr){
        std::unique_ptr temp_ptr(ptr);
        m_shared_cnt = new SharedCount();
        temp_ptr.reset();      
    }

或者使用 defer, 这里有我之前写的文章链接:https://blog.csdn.net/PX1525813502/article/details/127834364
当然引入异常机制也会带来一些问题, 比如会发生代码膨胀, 大约5%~15%。异常路径性能损失较大,也就是发生了异常后性能会降低, 比传错误码大很多。关于异常的详细的讨论参考吴老师的文章:https://zhuanlan.zhihu.com/p/6170882594
总结: 使用异常机制时, 异常的概率最好低于1%, 频繁抛异常效率就太低了。根据我的理解只在某些关键地方使用, 比如使用系统API打开文件, 服务程序的重启机制等。

三、错误码机制

  关于错误码, 我深刻体会到它的一个问题就是不同模块之间定义的错误码不同, 而且通常都是 int 整数类型的错误码, 当在一个编译单元内使用了含有不同定义错误码的多个SDK时,就比较方了, 而且通常不可以转换成对应字符串, 日志里面看到易读性就不高。这里可以借助标准库的 std::system_error 来配合使用, 它的主要成员有:
  ①枚举 errc, 几乎覆盖 POSIX Error 的错误码
  ②error_code 和 error_condition 两个类分别代表错误和通用错误
  ③支持特殊错误和一般错误的转换进行比较
所以可以把自定义的错误码转换成 std::system_error 里面的错误码, 用户直接将错误码和 std::system_error 标准库的错误码进行比较, 就避免了出现多套错误码的情形。下面结合直接上代码看看具体咋个用的。

3.1、集成错误码一 标识错误码

  假如自定义了一下错误码:

enum class DivError
{
    kInvalidParam,
    kDivideZero,
};

通过特化向标准库标记其为错误码:

template<>
struct std::is_error_code_enum<DivError>: true_type {};
3.2、集成错误码二 错误类别和输出

 作用是将枚举类型通过继承 std::error_category 接口转成对应的字符串。

class DivErrorCategory:public std::error_category
{
public:
    const char* name()const noexcept override 
    {
        return "Divide Error";
    }

    std::string message(int code) const override
    {
        switch (static_cast<DivError>(code))
        {
        case DivError::kSuccess:
            return "Success";
        
        case DivError::kInvalidParam:
            return "InvalidParam";
        
        case DivError::kDivideOverFlows:
            return "DivideOverFlow";
        }
         return "Unknown";
    }
};

覆写 std::error_category 基类的 const char* name() const noexcept 和 std::string message(int code) const 两个函数即可。

3.3、集成错误码三 错误转换成标准库错误码

 这里和第二步的流程类似, 转换成对应的 std::error_condition

    std::error_condition default_error_condition(int code) const noexcept override
    {
        switch (static_cast<DivError>(code))
        {
        case DivError::kSuccess:
            break;
        
        case DivError::kInvalidParam:
            return std::make_error_condition(std::errc::invalid_argument);

        case DivError::kDivideOverFlows:
            return std::make_error_condition(std::errc::value_too_large);  
        }

        return std::error_condition(code, *this);
    }   
3.4、集成错误码四 构造 error_code

 需要提供一个转换成 std::error_condition 的通用函数:

std::error_code make_error_code(DivError e)
{
    static DivErrorCategory diverror_category;
    return {static_cast<int>(e), diverror_category};
}

此时就可以把模块内定义的错误码与标准库的错误码关联起来了, 而且还能输出对应字符串。这个机制还可以参考 boost 库内的 boost::outcome。

####四 封装对象与返回值优化
  考虑如下代码:

struct BigObj
{
    BigObj(){std::cout<<"Big Obj Construct"<<std::endl;}
    ~BigObj(){std::cout<<"Big Obj deConstruct"<<std::endl;}
    BigObj(const BigObj& that){std::cout<<"Big Obj copy"<<std::endl;}
    void Op(){std::cout<<"Big Obj Op"<<std::endl;}
private:
   std::array<int, 1024> m_big_data;
};

BigObj GetBigObj(bool flag)
{
    if(!flag){
        std::runtime_error("BigObj bad flag");
    }
    BigObj obj;
    obj.Op();
    return obj;
}

根据第一节的知识, 假如有函数调用 GetBigObj(), 那么此时会有返回值优化, 期间 BigObj 只会构造一次, 但是当返回值类型被 std::optional 封装一次以后就不一定了。

std::optional<BigObj> GetBigObj(bool flag)
{
   if(!flag){
       return std::nullopt;
   }

   BigObj obj;
   obj.Op();
   return obj;
}

此时如果调用 GetBigObj() 返回对象就会发生一次对象拷贝。因为函数内出现了返回两种不同类型的参数, 编译器就无法以此来进行返回值优化。此时就需要进行改进一下, 代码如下所示:

std::optional<BigObj> GetBigObj(bool flag)
{
   std::optional<BigObj> result;
   if(!flag){
       return result;
   }

   result.emplace();
   result->Op();
   return result;
}

此时, 就可以一如既往的拥有返回值优化的高性能了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/577123.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MemFire解决方案-物联网数据平台解决方案

方案背景 随着各种通讯、传感技术发展&#xff0c;数据通讯成本的急剧下降&#xff0c;数以万亿计的智能设备&#xff08;智能手环、智能电表、智能手机、各种传感器设备等&#xff09;接入网络&#xff0c;并源源不断的产生海量的实时数据。这些海量数据的价值挖掘&#xff0…

美国言语听力学会(ASHA)关于非处方 (OTC) 助听器的媒体声明(翻译稿)

美国国会于 2021 年 4 月 13 日批准美国听力学会积极提供建议&#xff0c;并一直积极参与制定FDA关于非处方助听器销售的拟议法规。根据2017年通过的立法授权。学院积极参与帮助塑造授权立法&#xff0c;并就即将出台的条例分享了建议。 根据美国卫生与公众服务部NIH / NIDCD的…

软件工程的介绍

软件工程 这一章的内容其实还是蛮多的,大概一共有10个章节,分别是下面的一些内容,但是呢,这一章的内容其实是比较偏向文科类的,也就是说,记忆的内容其实占有很大的篇幅,在该考试科目当中呢,其实也是主要影响上午题部分的选择题的考察,基本的分值呢,在10分左右,分值占…

采购数据分析驾驶舱分享,照着它抄作业

今天我们来看一张采购管理驾驶舱。这是一张充分运用了多种数据可视化图表、智能分析功能&#xff0c;从物料和供应商的角度全面分析采购情况的BI数据可视化报表&#xff0c;主要分为三个部分&#xff0c;接下来就分部分来了解一下。 第一部分&#xff1a;关键指标计算及颜色预…

【linux高性能服务器编程】项目实战——仿QQ聊天程序源码剖析

hello &#xff01;大家好呀&#xff01; 欢迎大家来到我的Linux高性能服务器编程系列之项目实战——仿QQ聊天程序源码剖析&#xff0c;在这篇文章中&#xff0c;你将会学习到如何利用Linux网络编程技术来实现一个简单的聊天程序&#xff0c;并且我会给出源码进行剖析&#xff…

goroutinue和channel

goroutinue和channel 需求传统方式实现goroutinue进程和线程说明并发和并行go协程和go主线程MPG设置Go运行的cpu数 channel(管道)-看个需求使用互斥锁、写锁channel 实现 使用select可以解决从管道取数据的阻塞问题&#xff08;无需手动关闭channel了&#xff09;goroutinue中使…

【网络原理】TCP协议的连接管理机制(三次握手和四次挥手)

系列文章目录 【网络通信基础】网络中的常见基本概念 【网络编程】网络编程中的基本概念及Java实现UDP、TCP客户端服务器程序&#xff08;万字博文&#xff09; 【网络原理】UDP协议的报文结构 及 校验和字段的错误检测机制&#xff08;CRC算法、MD5算法&#xff09; 【网络…

SkyWalking 自定义Span并接入告警

图容易被CSDN吞掉&#xff0c;我在掘金也发了&#xff1a;https://juejin.cn/post/7361821913398837248 我就是这么膨胀 最近在做 OpenAI API 套壳&#xff0c;当我使用 okhttp-sse 这个库进行流式内容转发的时候&#xff0c;我发现有些回调方法 SkyWalking 不能抓取到。这就…

JS面试题汇总(十)

JavaScript 的数据对象有那些属性值&#xff1f; writable&#xff1a;这个属性的值是否可以改。 configurable&#xff1a;这个属性的配置是否可以删除&#xff0c;修改。 enumerable&#xff1a;这个属性是否能在 for…in 循环中遍历出来或在 Object. keys 中列举出来。 …

小程序评分/关键词/UV优化助力小程序登顶

随着小程序市场的日益繁荣&#xff0c;小程序搜索排名优化成为了众多开发者关注的焦点。小程序搜索排名被很多因素影响着&#xff0c;关键词、评分还有uv&#xff08;授权&#xff09;等。在本文小柚和各位老板分享如何有效优化小程序搜索排名的经验。 一、关键词策略 关键词是…

[最新]CentOS7设置开机自启动Hadoop集群

安装好Hadoop后我们可以使用开机自启动的方式&#xff0c;节约敲命令的时间。注意是centOS7版本!!!和centOS6版本区别非常大!!! 1、切换到系统目录 [rootmaster ~]# cd /etc/systemd [rootmaster systemd]# ll total 32 -rw-r--r-- 1 root root 720 Jun 30 23:11 bootcha…

汽车新智能图谱里:理解腾讯的AI TO B路径

将自身的C2B产品和产业理解充分AI化&#xff0c;在自身内部场景率先验证跑通后&#xff0c;进而释放给产业伙伴&#xff0c;对应到具体的需求痛点&#xff0c;一起打磨对应的行业AI模型。 这也恰是腾讯“实用”标签背后的AI产业路径。 作者|皮爷 出品|产业家 成本、性价…

DS进阶:并查集

一、并查集的原理 在一些应用问题中&#xff0c;需要将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个单元素集合&#xff0c;然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一个元素归属于那个集合的运算。适合于描述这…

Taro +vue3 中实现全局颜色css变量的设置和使用

当我们现在需要弄一个随时修改的页面颜色主题色 我们可以随时修改 我使用的是 Taro 框架 一般有一个app.less 文件 我们在这个里面 设置一个root 全局样式 :root {--primary-color: #028fd4;--secondary-color: #028fd6;/* 添加其他颜色变量 */ } 这样在全局我们就可以使用这…

uniapp 微信小程序 分享海报的实现

主页面 <template><view class"page"><!-- 自定义导航栏--><Navbar title"我的海报"></Navbar><view class"container"><poster ref"poster" :imageUrl"image" :imageWidth"7…

python之List列表

1. 高级数据类型 Python中的数据类型可以分为&#xff1a;数字型&#xff08;基本数据类型&#xff09;和非数字型&#xff08;高级数据类型&#xff09; 数字型包含&#xff1a;整型int、浮点型float、布尔型bool、复数型complex 非数字型包含&#xff1a;字符串str、列表l…

探索和构建 LLaMA 3 架构:深入探讨组件、编码和推理技术(四)分组多查询注意力

探索和构建 LLaMA 3 架构&#xff1a;深入探讨组件、编码和推理技术&#xff08;四&#xff09;分组多查询注意力 Grouped-query Attention&#xff0c;简称GQA 分组查询注意力&#xff08;Grouped-query Attention&#xff0c;简称GQA&#xff09;是多查询和多头注意力的插值…

【35分钟掌握金融风控策略10】风控策略部署2

目录 策略部署 决策引擎系统简介 基于决策引擎进行策略部署 策略部署结果验证 知识点补充 测试验证 回溯比对 策略部署 策略主要部署在决策引擎上进行风险决策&#xff0c;接下来分别介绍决策引擎系统&#xff0c;以及基于决策引擎进行策略部署的相关内容。 决策引擎系…

java-Spring-(MyBatis框架-xml管理)

目录 前置条件 xml与注解比较 1.1 xml定义 1.2 和SQL注解比较 建包准备 插入数据 ​编辑 更新数据 删除数据 查询数据 查看单字段查询 &#x1f3f7;&#x1f4a3;前置条件 创建一个spring boot 初始化的项目 &#x1f3f7;&#x1f4a3;xml与注解比较 1.1 xml定义 …

微信小程序简单实现购物车功能

微信小程序简单实现购物车结算和购物车列表展示功能 实现在微信小程序中对每一个购物车界面的商品订单&#xff0c;进行勾选结算和取消结算的功能&#xff0c;相关界面截图如下&#xff1a; 具体实现示例代码为&#xff1a; 1、js代码&#xff1a; Page({/*** 页面的初始数…
最新文章