当前位置:首页 > 其他 > 正文内容

C++ 中心攻略 —— 功能

邻居的猫1个月前 (12-09)其他1385

C++ 中心攻略 —— 功用

阅览主张:先阅览 《功用优化的一般战略及办法》

到现在,C++ Core Guidelines 中关于功用优化的主张共有 18 条,而其间很大一部分是劝诫你,不要简略优化!

非必要,不优化

  • Per.1: 不要无故优化
  • Per.2: 不要过早优化
  • Per.3: 只优化少量要害代码

前三条能够总结为:非必要,不优化。所谓的“优化”,是指献身可读性、可保护性,以交换功用提高(不然应该作为编程的规范实践)。优化或许引进新的 bug,添加保护本钱。软件工程师应把重心放在编写简练、易于了解和保护的代码,而不是把功用作为首要方针。

先丈量,再优化

假如功用非常重要,应该经过精确地丈量,找到程序的 hot spots,再有针对性地优化。

Per.4: 不要假定杂乱的代码比简略的代码快

  • 多线程未必比单线程快:考虑到线程间同步的开支、上下文切换开支,多线程未必比单线程快
  • 运用一系列杂乱的优化技巧编写的杂乱代码未必比直接编写的简略代码快,如
// 好:简略直接
vector<uint8_t> v(100000);

for (auto& c : v)
    c = ~c;
// 欠好:杂乱的优化技巧,本意想更快,但往往更慢!
vector<uint8_t> v(100000);

for (size_t i = 0; i < v.size(); i += sizeof(uint64_t)) {
    uint64_t& quad_word = *reinterpret_cast<uint64_t*>(&v[i]);
    quad_word = ~quad_word;
}

Per.5: 不要假定初级言语比高档言语快

不要小看编译器的优化才干,许多时分编译器发生的代码要比手动编写初级言语更高效!

Per.6: 没有丈量就不要对功用妄下断语

  • 功用优化许多时分是反直觉的,针对某些条件下的功用优化技巧在另一个环境下或许会劣化功用,因而必需求丈量才知道某个改动到底会“优化”仍是“劣化”功用
  • 小于 4% 的代码能占用 50% 的程序履行时刻。只要丈量才知道时刻花在哪里,才干有针对性地优化

以上 6 条主张在 《功用优化的一般战略及办法》 中有更详细的描绘。

详细优化主张

Per.7 规划应当答应优化

假如规划之初彻底忽视了将来优化的或许性,会导致很难修正。

过早优化是万恶之源,但这并不是小看功用的托言。一些经过时刻查验的最佳实践能够协助咱们写出高效、可保护、可优化的代码:

  • 信息传递:接口规划要洁净,但还要带着满意的信息,以便后续改善完成。
  • 紧凑的数据结构:默许情况下,运用紧凑的数据结构,如 std::vector,假如你以为需求一个链表,测验规划接口运用户看不到这个结构(参阅规范库算法的接口规划)。
  • 函数参数的传递和回来:区别可变和不行变数据。不要把 资源管理 的使命强加给用户。不要把设想的 indirection 强加给用户。运用惯例的办法传递信息,非惯例或为特定完成“优化”过的数据传递办法或许会导致后续难以修正完成。
  • 笼统:不要过度泛化。企图满意每种或许的运用情况(包含误用),把每个规划决议计划推延(编译或运转时 indirection)会导致杂乱、臃肿、难以了解。不要对未来需求的猜想来进行泛化,从详细示例中进行泛化。泛化时坚持功用,抱负状况是零开支泛化。
  • 库:挑选具有杰出接口规划的库。假如没有现成的,自己写一个,仿照具有杰出接口风格的库(能够从规范库找创意)。
  • 阻隔:把你的代码和旧的、乱的代码阻隔开。能够依照自己的风格,规划一个接口风格杰出的 wrapper,把那些不得不必的旧的、乱的代码封装起来,不要污染到咱们自己的代码。

"indirection"(直接)一般指的是经过引进额定的层级或中介来拜访数据或功用。在 C++ 中,这或许触及运用指针、引证或其他直接办法来拜访变量、目标或函数。

  1. 规划接口时,不要只考虑第一版的用例和完成。初版完成之后,有必要 review,因为一旦布置之后,补偿错误将很困难。
  2. 初级言语并不总是高效,高档言语的代码不一定慢。
  3. 任何操作都有开支,不必过火忧虑开支(现代核算机都满意的快),可是需求大致了解各种操作的开支。例如:内存拜访、函数调用、字符串比较、体系调用、磁盘拜访、网络通信。
  4. 不是每段代码都需求安稳接口,有的接口或许仅仅完成细节。但仍是要停下来想一下:假如要运用多个线程完成这个操作,需求什么样的接口?是否能够向量化?
  5. 本条目和 Per.2 并不矛盾,而是它的弥补:鼓舞开发者在必要且时机成熟时进行优化。

移动语义

《C++ Core Guidelines 解析》针对本条目要点弥补了移动语义:写算法时,应运用移动语义,而不是复制。移动语义有以下优点:

  • 移动开支比复制低
  • 算法安稳,因为不需求分配内存,不会呈现 std::bad_alloc 反常
  • 算法能够用于“只移类型”,如 std::unique_ptr

需求移动语义的算法遇到不支撑移动操作类型,则主动“回退”到复制操作。
而只支撑复制语义的算法遇到不支撑复制操作的类型时,则编译报错。

Per.10 依靠静态类型体系

弱类型(如 void* )、初级代码(如把 sequence 作为独自的字节来操作)会让编译器难以优化。

《解析》中还给出了一些额定的协助编译器生成优化代码的技巧:

  1. 本地代码。“本地”指在同一个编译单元(好像一个 .c/.cpp 文件中)。例如 std::sort 需求一个谓词,传入本地 lambda 或许会比传入函数(指针)更快。
    因为关于本地 lambda,编译器具有一切可用的信息来生成最优代码,而函数或许界说在另一个编译单元中,编译器无法获取有关该函数的细节,然后无法进行深度优化。
  2. 简略代码。优化器会搜索能够被优化的已知形式,简略的代码更简略被匹配到。假如是手写的杂乱代码,反而或许失去让编译器优化的时机。
  3. 额定提示。constnoexceptfinal 等要害字能够给编译器供给额定的信息,有了这些额定的信息,编译器能够斗胆地做进一步优化。当然要先搞清楚这些要害字的意义及发生的影响。

Per.11 将核算从运转时提前到编译期

能够削减代码尺度和运转时刻、防止数据竞赛、削减运转期的错误处理。

constexpr

将函数声明为 constexpr,且参数都是常量表达式,则能够在编译期履行。

留意:constexpr 函数能够在编译期履行,但不意味着只能在编译期履行,也能够在运转期履行。

constexpr 函数的约束:

  • 不能运用 staticthread_local 变量
  • 不能运用 goto
  • 不能运用反常
  • 一切变量有必要初始化为字面类型

字面类型:

  • 内置类型(及其引证)
  • constexpr 结构的类
  • 字面类型的数组

例 1

// 旧风格:动态初始化
double square(double d) { return d*d; }
static double s2 = square(2);

// 现代风格:编译期初始化
constexpr double ntimes(double d, int n)   // 假定 0 <= n
{
    double m = 1;
    while (n--) m *= d;
    return m;
}
constexpr double s3 {ntimes(2, 3)};

第一种写法很常见,但有两个问题:

  • 运转时函数调用开支
  • 另一个线程或许在 s2 初始化之前拜访 s2

注:常量不存在数据竞赛的问题

例 2

一个常用的技巧,小目标直接存在 handle 里,大目标存在堆上。

constexpr int on_stack_max = 20;

// 直接存储
template<typename T>
struct Scoped {
    T obj;
};

// 在堆上存储
template<typename T>
struct On_heap {
    T* objp;
};

template<typename T>
using Handle = typename std::conditional<
    (sizeof(T) <= on_stack_max),
    Scoped<T>,
    On_heap<T>
>::type;

void f()
{
    // double 在栈上
    Handle<double> v1;
    // 数组在堆上
    Handle<std::array<double, 200>> v2;
}

编译期能够核算出最佳类型,类似地技能也可用于在编译期挑选最佳函数。

实际上大多数核算取决于输入,不或许把一切的核算悉数放到编译期。除此之外,杂乱的编译期核算或许大幅添加编译时刻,而且导致调试困难。甚至在很少场景下,或许导致功用劣化。

代码查看主张

  • 查看是否有简略的、能够作为(但没有) constexpr 的函数
  • 查看是否有函数的一切参数都是常量表达式
  • 查看是否有能够改为 constexpr 的宏

Per.19 以可猜测的办法拜访内存

缓存对功用影响很大,一般缓存算法对相邻数据的简略、线性拜访功率更高。

当程序需求从内存中读取一个 int 时,现代核算机架构会一次读取整个缓存行(一般 64 字节),储存在 CPU 缓存中,假如接下来要读取的数据已经在缓存中,则会直接运用,快许多。

例如:

int matrix[rows][cols];

// 欠好
for (int c = 0; c < cols; ++c)
    for (int r = 0; r < rows; ++r)
        sum += matrix[r][c];

// 好
for (int r = 0; r < rows; ++r)
    for (int c = 0; c < cols; ++c)
        sum += matrix[r][c];

在 C++ 规范库中,std::vector, std::array, std::string 将数据存在接连的内存块中的数据结构对缓存行很友爱。而 std::liststd::forward_list 则恰恰相反。
例如在某测验环境中,从容器中读取并累加一切元素:

  • std::vectorstd::liststd::forward_list 快 30 倍
  • std::vectorstd::deque 快 5 倍

许多场景下,即便需求在中心刺进/删去元素,因为缓存行的原因,std::vector 的功用也或许好于 std::list

除非丈量的结果表明其他容器功用好于 std::vector,不然应将 std::vector 作为首选容器。

其他

剩余的条目到现在还只要标题,短少详细描绘:

  • Per.12 Eliminate redundant aliases/消除冗余别号
  • Per.13 Eliminate redundant indirections/消除冗余直接
  • Per.14 Minimize the number of allocations and deallocations/尽或许削减分配和开释
  • Per.15 Do not allocate on a critical branch/不在要害分支上分配
  • Per.16 Use compact data structures/运用紧凑的数据结构:功用主要由内存拜访决议
  • Per.17 Declare the most used member of a time-critical struct first/关于时刻要害的结构体,把最常用的成员界说在前
  • Per.18 Space is time/空间便是时刻:功用主要由内存拜访决议
  • Per.30 Avoid context switches on the critical path/防止要害途径上的上下文切换

总结

  • 非必要,不优化
  • 先丈量,再优化
  • 为编译器优化供给必要信息:
    • 正确运用 constfinalnoexcept 等要害字
    • 为函数完成移动语义、假如或许,使之成为 constexpr
  • 现代核算机架构为接连读取内存而进行了优化,应该将 std::vector, std::array, std::string 作为首选

Reference

  • C++ Core Guidelines, Per: Performance
  • 《功用优化的一般战略及办法》
  • 《C++ Core Guidelines 解析》

扫描二维码推送至手机访问。

版权声明:本文由51Blog发布,如需转载请注明出处。

本文链接:https://www.51blog.vip/?id=728

分享给朋友:

“C++ 中心攻略 —— 功能” 的相关文章

【译文】怎么了解导数:乘积,幂次和链式法则

【译文】怎么了解导数:乘积,幂次和链式法则

(以下大部分机翻,仅供个人兴趣学习) 我从来没有真实了解过那些杂乱无章的求导规矩。加法规律,乘法规律,除法规律——它们是怎么结合在一起的? 以下是我对导数的观念: 咱们有一个体系来剖析,咱们的函数f 导数f (又叫df/dx)是逐时刻行为 事实证明,f是一个体系的一部分(h = f+g) 运用部分的...

Ubuntu下装备装置Sundials微分方程求解器

Ubuntu下装备装置Sundials微分方程求解器

Chapter 1 获取Sundials及装置环境装备 Section 1.1 获取Sundials (1) Linux体系下运用指令git clone https://github.com/LLNL/sundials从github获取Sundials库房,或许转到[https://github.c...

第一章 FFmpeg初体验:在Centos7.9下编译FFmpeg!

第一章 FFmpeg初体验:在Centos7.9下编译FFmpeg!

FFmpeg 官方网站:https://ffmpeg.org//download.html#build-linux 1.下载源码 1.1 第一种方法,官网上面下载源码包: 到现在最新的版别是7.0.1,对应的地址是:https://ffmpeg.org//releases/ffmpeg-7.0.1....

开源国际,推动全球创新与合作的新引擎

“开源国际”可能指的是不同的实体或组织,具体取n 分支公司:总部位于上海,并在天津、青岛、宁波、厦门、广州、深圳和香港设有分支机构。 具体公司:例如,青岛开源国际物流有限公司成立于2019年,位于青岛市市南区,提供无船承运业务和货运代理服务。宁波开源国际物流有限公司成立于2002年,位...

开源项目管理系统,开源项目管理系统的优势与应用

开源项目管理系统,开源项目管理系统的优势与应用

1. Redmine: 特点:Redmine是一个基于Web的项目管理工具,支持多种项目管理方法,如敏捷、瀑布等。它提供了任务管理、问题跟踪、文档管理等功能。 适用砛n 开源项目管理系统的优势与应用一、开源项目管理系统的定义与特点开源项目管理系统是指基于开源协议,由开发者共同维护和改进...

北京超算云计算中心,驱动数字经济的高性能计算引擎

北京超算云计算中心,驱动数字经济的高性能计算引擎

北京超级云计算中心(简称“北京超算”)是由北京市人民政府主导、院市共建的国家重要信息化基础平台,成立于2011年。该中心坐落于北京市怀柔综合性国家科学中心怀柔科学城,依托中国科学院计算机网络信息中心建设并提供技术支撑,运营主体为北京北龙超级云计算有限责任公司。北京超算以立足北京、辐射全国、构建国内领...