我的编程空间,编程开发者的网络收藏夹
学习永远不晚

性能优化利器之Constexpr

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

性能优化利器之Constexpr

最近在升级系统和进行一些性能优化,业余时间也看一些技术书籍和视频,看了下上次更新文章的时间,大致在一个月前了,确实有点久了,所以赶紧拾起来,不能让大伙忘了我不是😁。

今天,聊聊在升级过程中的一个比较重要的优化点-编译期优化。

概述

说明符constexpr是自C++11引入,我相信很多人跟我一样,在第一次接触这个的时候,会很容易和const混淆。

从概念上理解的话,constexpr即常量表达式,重点在表达式字段,用于指定变量或函数可以在常量表达式中使用,可以(或者说一定)在编译时求值的表达式,而const则为了约束变量的访问控制,表示运行时不可以直接被修改,其往往可以在编译期和运行时进行初始化。

前面提到了constexpr是在编译阶段进行求值,那么也就是说在程序运行之前,就已经计算完成,这种无疑大大提升了程序的运行效率。因此提升运行效率就是C++11引入constexpr说明符的目的,也就是说能在编译阶段做的事情就绝不放在运行期做。

变量

代码如下:

example1.cc

int main() {
    const int val = 1 + 2;
    return 0;
}

上述代码汇编结果如下:

main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 3
        mov     eax, 0
        pop     rbp
        ret

从上述汇编结果可以看出,在编译阶段就将val赋值成3,也就是说在编译阶段完成了求值操作。

再看另外一个示例2:

example2.cc

int Add(const int a, const int b) {
    return a + b;
}

int main() {
    const int val = Add(1, 2);
    return 0;
}

同样的,其汇编如下:

Add(int, int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        mov     edx, DWORD PTR [rbp-4]
        mov     eax, DWORD PTR [rbp-8]
        add     eax, edx
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     esi, 2
        mov     edi, 1
        call    Add(int, int)
        mov     DWORD PTR [rbp-4], eax
        mov     eax, 0
        leave
        ret

分析上述汇编,发现并没有在编译阶段进行求值,所以也就是说上述的求值过程将会延后至编译期进行。

好了,既然示例一(使用const)可以在编译期进行求值,而constexpr也可以在编译期求值,那么直接用constexpr替换示例一种的const是否可行?

example3.cc

int main() {
    constexpr int val = 1 + 2;
    return 0;
}

接着看下汇编代码:

main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 3
        mov     eax, 0
        pop     rbp
        ret

呃😓,与示例一完全一样。。。

在上面示例2中,通过汇编代码发现其是在运行期求值,那么有没有办法在编译期求值呢?那就是使用constexpr表达式:

example4.cc

constexpr int Add(const int a, const int b) {
    return a + b;
}

int main() {
    const int val = Add(1, 2);
    return 0;
}

汇编如下:

main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 3
        mov     eax, 0
        pop     rbp
        ret

有没有发现很眼熟,对,跟示例1和示例3的结果一样,该代码较示例2的唯一区别是多了个constexpr说明符,但将求值时期从运行期放到了编译期,可想而知,效率提升那是杠杠的。。。😁

函数

constexpr也可以修饰普通函数或者成员函数,其实这块在上一节已经有提过,示例如下:

constexpr int Add(const int a, const int b) {
    return a + b;
}

int main() {
    const int val = Add(1, 2);
    int val1 = 3;
    int val2 = Add(val, val1);
    return 0;
}

汇编如下:

Add(int, int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        mov     edx, DWORD PTR [rbp-4]
        mov     eax, DWORD PTR [rbp-8]
        add     eax, edx
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 3
        mov     DWORD PTR [rbp-8], 3
        mov     eax, DWORD PTR [rbp-8]
        mov     esi, eax
        mov     edi, 3
        call    Add(int, int)
        mov     DWORD PTR [rbp-12], eax
        mov     eax, 0
        leave
        ret

从上述汇编代码可以看出,val的求值是在编译阶段,而val2的求值则是在运行阶段,这是因为其引入了一个非const变量val1。

通过本示例,可以看出,将函数声明为constexpr可以提示效率,让编译器来决定是在编译阶段还是运行阶段来进行求值,当然了,如果想了解在编译阶段求值的各种细节规则,请参考constexpr in cppreference。

if语句

如果您目前使用C++11进行编码,那么需要仔细阅读本节,这样可以为将来的版本升级打好基础;如果您正在使用C++17进行编码,那么更得阅读本节,相信读完本节后,会有一个不一样的认识😁。

自C++17起,引入了if constexpr语句,在本节中,将借助SFINAE 和 std::enable_if来实现一个简单的Square功能,最后借助if constexpr对代码进行优化(如果对SFINAE 和 std::enable_if不是很了解的,建议自行阅读哈)。

如果有个需求,实现一个Add函数,其既支持算术类型又支持用户自定义类型:

template 
struct Number {
    Number(const T& _val) :
        value(_val) {}

    T value;
};

template
T Square(const T& t) {
    return t + t;
}

int main() {
  int i = 5;
  float f = 5.0;
  bool b = true;
  Number n(5);

  auto res = Square(i); // 调用int Add(int);
  auto res2 = Square(f); // 调用 float Add(float);
  auto res3 = Square(b);  // call bool Square(bool);
  auto res4 = Square(n); //编译失败,因为Number<>没有提供operator*操作
}

上述代码编译出错,因为Number<>没有提供operator*操作,所以这个时候第一个想法是修改Square函数,如下:

template
T Square(const T& t) {
    if (std::is_arithmetic::value) {
        return t * t;
    } else {
        return t.value * t.value;
    }
}

在上述代码中,如果T是算数类型,则直接进行*操作,否则取其value进行*操作。

将上述代码进行编译,报错如下:

example5.cc: In instantiation of ‘T Square(const T&) [with T = int]’:
example5.cc:26:20:   required from here
example5.cc:16:18: error: request for member ‘value’ in ‘t’, which is of non-class type ‘const int’
   16 |         return t.value * t.value;
      |                ~~^~~~~
example5.cc:16:28: error: request for member ‘value’ in ‘t’, which is of non-class type ‘const int’
   16 |         return t.value * t.value;
      |                          ~~^~~~~
....

以Square(i)为例,这是因为在编译的时候,会尝试int.value操作,显然int.value不存在,这就导致了上述的错误输出,为了更为清楚的显示本错误,将Square()修改如下:

int Square(const int& t) {
    if (true) {
        return t * t;
    } else {
        return t.value * t.value;
    }
}

这样就能很清楚的知道为什么编译失败了,因为在代码中存在t.value * t.value操作,而对于一个int来说并没有value这个变量,所以编译失败。

为了解决这个问题,我们尝试引入std::enable_if操作,如下:

template
typename std::enable_if::value, T>::type Square(const T& t) {
    return t * t;
}

template
typename std::enable_if::value, T>::type Square(const T& t) {
    return t.value * t.value;
}

现在有两个函数模板,如果是算术类型,则调用第一个,否则调用第二个,完整代码如下:

#include 

template
typename std::enable_if::value, T>::type Square(const T& t) {
    return t * t;
}

template
typename std::enable_if::value, T>::type Square(const T& t) {
    return t.value * t.value;
}

template 
struct Number {
    Number(const T& _val) :
        value(_val) {}

    T value;
};

int main() {
  int i = 5;
  float f = 5.0;
  bool b = true;
  Number n(5);

  auto res = Square(i); // 调用int Add(int);
  auto res2 = Square(f); // 调用 float Add(float);
  auto res3 = Square(b);  // call bool Square(bool);
  auto res4 = Square(n); // 成功
  
  return 0;
}

上述代码编译成功。

在上述代码中,为了编译成功,我们引入了两个Square()模板函数借助std::enable_if来实现,代码上多少有点冗余,在这个时候,本节的主角if constexpr 出场,完整代码如下:

#include 
template
T Square(const T& t) {
    if constexpr (std::is_arithmetic::value) {
        return t * t;
    } else {
        return t.value * t.value;
    }
}

template 
struct Number {
    Number(const T& _val) :
        value(_val) {}

    T value;
};

int main() {
  int i = 5;
  float f = 5.0;
  bool b = true;
  Number n(5);

  auto res = Square(i); // 调用int Add(int);
  auto res2 = Square(f); // 调用 float Add(float);
  auto res3 = Square(b);  // call bool Square(bool);
  auto res4 = Square(n); // 成功
  
  return 0;
}

编译成功。

我们借助一个Square()函数模板以及更加符合编码习惯的if语句就能解决上面的问题,且比使用std::enable_if方式更为优雅和符合阅读习惯,进而提高代码的可阅读性。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

性能优化利器之Constexpr

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

性能优化利器之Constexpr

说明符constexpr​是自C++11引入,我相信很多人跟我一样,在第一次接触这个的时候,会很容易和const混淆。

服务器性能优化之网络性能优化

今天分享一篇后台服务器性能优化之网络性能优化,希望大家对Linux网络有更深的理解。

性能调优必备利器之JMH

本文主要介绍了性能基准测试工具 JMH,它可以通过一些功能来规避由 JVM 中的 JIT 或者其他优化对性能测试造成的影响。

性能优化之window.onload

最近在做一些性能优化相关的工作,相信大家在工作过程中也会遇到一些性能优化相关的场景,这对于前端开发者来讲是一项加分技能。为了我们的用户在使用我们的产品时能够有一个非常好的体验,我们需要对页面进行诊断优化。在行业中,我们的页面P90在两秒内算

Android性能优化之网络优化

在移动互联网的快速发展环境下,手机用户日益对网络的使用或体验有着更深度的诉求,因此应用中的网络体验已经显得由此重要。

Android性能优化之弱网优化详解

这篇文章主要为大家介绍了Android性能优化之弱网优化示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

React性能优化之useMemo、useCallback

记忆化用于提高React应用程序的性能,记忆化用于跳过组件的不必要重新渲染。useMemo和useCallback​钩子分别用于记忆化返回值和函数声明。虽然useMemo和useCallback​的行为类似,但两者之间存在关键差异。useM

Android性能优化之Bitmap图片优化详解

前言 在Android开发过程中,Bitmap往往会给开发者带来一些困扰,因为对Bitmap操作不慎,就容易造成OOM(Java.lang.OutofMemoryError - 内存溢出),本篇博客,我们将一起探讨Bitmap的性能优化。
2022-06-06

Android性能优化之运算篇

运算篇1) Intro to Compute and Memory ProblemsAndroid中的Java代码会需要经过编译优化再执行的过程。代码的不同写法会影响到Java编译器的优化效率。例如for循环的不同写法会对编译器优化这段代码
2022-06-06

Ceph OSD CPU 性能优化 之一

目前,我们正在进行多种方法来优化 Ceph 的数据路径,但现实情况是 Ceph 一直都是需要相当多的 CPU 才能充分发挥出比如像 NVMe 这样高速存储设备的性能。
Ceph数据CPU2024-12-13

Golang函数性能优化之存储分配优化

为了提高 go 函数性能,存储分配优化至关重要。通过预分配缓冲区、使用切片和使用对象池等技术,可以有效减少内存分配带来的开销。以读取大文件为例,预分配文件行缓冲区可以显著优化性能,因为它减少了频繁的内存分配。Go 语言函数性能优化:存储分配
Golang函数性能优化之存储分配优化
2024-04-16

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录