c/c++代码性能效率

2023-05-16


一、尽量减少值传递,多用引用来传递参数

boolCompare(string s1, string s2)
boolCompare(string *s1, string *s2)
boolCompare(string &s1, string &s2)
boolCompare(const string &s1, const string &s2)

  其中若使用第一个函数(值传递),则在参数传递和函数返回时,需要调用string的构造函数和析构函数两次(即共多调用了四个函数),而其他的三个函数(指针传递和引用传递)则不需要调用这四个函数。因为指针和引用都不会创建新的对象。如果一个构造一个对象和析构一个对象的开销是庞大的,这就是会效率造成一定的影响。

  引用是一个变量的别名,对其操作等同于对实际对象操作,所以当你确定在你的函数是不会或不需要变量参数的值时,就大胆地在声明的前面加上一个const吧,就如最后的一个函数声明一样。同时加上一个const还有一个好处,就是可以对常量进行引用,若不加上const修饰符,引用是不能引用常量的。

二、循环

  循环内定义,还是循环外定义对象:

  如果调用赋值操作函数的开销比调用构造函数和析构函数的总开销小,则第一种效率高,否则第二种的效率高。

  避免过大的循环:

代码1:

for(inti = 0; i < n; ++i)
{
  fun1();
  fun2();
}

代码2:


for(inti = 0; i < n; ++i)
{
  fun1();
}
for(inti = 0; i < n; ++i)
{
  fun2();
}

  这就要看fun1和fun2这两个函数的规模(或复杂性)了,如果这多个函数的代码语句很少,则代码1的运行效率高一些,但是若fun1和fun2的语句有很多,规模较大,则代码2的运行效率会比代码1显著高得多。

  如果fun1和fun2的代码量很大,例如都大于Cache的容量,则在代码1中,就不能充分利用Cache了(由时间局部性和空间局部性可知),因为每循环一次,都要把Cache中的内容踢出,重新从内存中加载另一个函数的代码指令和数据,而代码2则更很好地利用了Cache,利用两个循环语句,每个循环所用到的数据几乎都已加载到Cache中,每次循环都可从Cache中读写数据,访问内存较少,速度较快,理论上来说只需要完全踢出fun1的数据1次即可。

  基本上不会在自己的主循环里搞什么运算工作,绝对是先计算好了,再到循环里查表;

  对于一些不需要循环变量参加运算的任务可以把它们放到循环外面,这里的任务包括表达式、函数的调用、指针运算、数组访问等,应该将没有必要执行多次的操作全部集合在一起,放到一个init的初始化程序中进行。

延时函数 :

通常使用的延时函数均采用自加的形式:
    void delay (void)
    {
unsigned int i;
    for (i=0;i<1000;i++) ;
    }
将其改为自减延时函数:
    void delay (void)
    {
unsigned int i;
        for (i=1000;i>0;i--) ;
    }
//两个函数的延时效果相似,但几乎所有的C编译对后一种函数生成的代码均比前一种代码少
//1~3个字节,因为几乎所有的MCU均有为0转移的指令,采用后一种方式能够生成这类指令。

  使用do…while循环编译后生成的代码的长度短于while循环;
  把相关循环放到一个循环里,也会加快速度。

switch:

  Switch语句中根据发生频率来进行case排序,Switch 可能转化成多种不同算法的代码。其中最常见的是跳转表和比较链/树。当switch用比较链的方式转化时,编译器会产生if-else-if的嵌套代码,并按照顺序进行比较,匹配时就跳转到满足条件的语句执行。所以可以对case的值依照发生的可能性进行排序,把最有可能的放在第一位,这样可以提高性能。此外,在case中推荐使用小的连续的整数,因为在这种情况下,所有的编译器都可以把switch 转化成跳转表。

  将大的switch语句转为嵌套switch语句 ;

if esle:

  要提升循环的性能,减少多余的常量计算非常有用(比如,不随循环变化的计算)。不好的代码(在for()中包含不变的if()):

for( i... )
{
  if( CONSTANT0 )
  {
    DoWork0( i )// 假设这里不改变CONSTANT0的值
  }
  else
  {
    DoWork1( i )// 假设这里不改变CONSTANT0的值
  }
}
推荐的代码:
if( CONSTANT0 )
{
  for( i...)
  {
    DoWork0( i );
  }
}
else
{
  for( i...)
  {
    DoWork1( i );
  }
} 

  无限循环:for (;;)指令少,不占用寄存器,而且没有判断、跳转,比while (1)好。


三、局部变量VS静态变量

  局部变量存在于堆栈中最大的好处是,函数能重复使用内存,当一个函数调用完毕时,退出程序堆栈,内存空间被回收,当新的函数被调用时,局部变量又可以重新使用相同的地址。当一块数据被反复读写,其数据会留在CPU的一级缓存(Cache)中,访问速度非常快。而静态变量却不存在于堆栈中。可以说静态变量是低效的。


四、避免使用多重继承

  在C++中,支持多继承,即一个子类可以有多个父类。书上都会跟我们说,多重继承的复杂性和使用的困难,并告诫我们不要轻易使用多重继承。其实多重继承并不仅仅使程序和代码变得更加复杂,还会影响程序的运行效率。

  这是因为在C++中每个对象都有一个this指针指向对象本身,而C++中类对成员变量的使用是通过this的地址加偏移量来计算的,而在多重继承的情况下,这个计算会变量更加复杂,从而降低程序的运行效率。而为了解决二义性,而使用虚基类的多重继承对效率的影响更为严重,因为其继承关系更加复杂和成员变量所属的父类关系更加复杂。


五、函数优化

(1)将小粒度函数声明为内联函数(inline):

  内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名。一般在代码中用inline修饰,但是能否形成内联函数,需要看编译器对该函数定义的具体处理。

  调用函数是需要保护现场,为局部变量分配内存,函数结束后还要恢复现场等开销,而内联函数则是把它的代码直接写到调用函数处,所以不需要这些开销,但会使程序的源代码长度变大。
  所以若是小粒度的函数,如下面的Max函数,由于不需要调用普通函数的开销,所以可以提高程序的效率。

int Max(inta, intb)
{
  returna>b?a:b;
}

(2)不定义不使用的返回值

  函数定义并不知道函数返回值是否被使用,假如返回值从来不会被用到,应该使用void来明确声明函数不返回任何值。

(3)减少函数调用参数

  使用全局变量比函数传递参数更加有效率。这样做去除了函数调用参数入栈和函数完成后参数出栈所需要的时间。然而决定使用全局变量会影响程序的模块化和重入,故要慎重使用。

(4)所有函数都应该有原型定义

  一般来说,所有函数都应该有原型定义。原型定义可以传达给编译器更多的可能用于优化的信息。

(5)尽可能使用常量(const)

  尽可能使用常量(const)。C++ 标准规定,如果一个const声明的对象的地址不被获取,允许编译器不对它分配储存空间。这样可以使代码更有效率,而且可以生成更好的代码。

(6)把本地函数声明为静态的(static)

  如果一个函数只在实现它的文件中被使用,把它声明为静态的(static)以强制使用内部连接。否则,默认的情况下会把函数定义为外部连接。这样可能会影响某些编译器的优化——比如,自动内联。


六、指针和数组索引

  用指针运算代替数组索引,这样做常常能产生又快又短的代码。与数组索引相比,指针一般能使代码速度更快,占用空间更少。使用多维数组时差异更明显。下面的代码作用是相同的,但是效率不一样。

 for(;;){                
    A= array[t++];
 } 

p=arrayfor(;;){
     a= *(p++); 
 }

  指针方法的优点是,array的地址每次装入地址p后,在每次循环中只需对p增量操作。在数组索引方法中,每次循环中都必须根据t值求数组下标的复杂运算。


七、使用尽量小的数据类型

  能够使用字符型(char)定义的变量,就不要使用整型(int)变量来定义;能够使用整型变量定义的变量就不要用长整型(long int),能不使用浮点型(float)变量就不要使用浮点型变量。当然,在定义变量后不要超过变量的作用范围,如果超过变量的范围赋值,C编译器并不报错,但程序运行结果却错了,而且这样的错误很难发现。

  在ICCAVR中,可以在Options中设定使用printf参数,尽量使用基本型参数(%c、%d、%x、%X、%u和%s格式说明符),少用长整型参数(%ld、%lu、%lx和%lX格式说明符),至于浮点型的参数(%f)则尽量不要使用,其它C编译器也一样。在其它条件不变的情况下,使用%f参数,会使生成的代码的数量增加很多,执行速度降低。


八,运算

  求余:位操作只需一个指令周期即可完成,而大部分的C编译器的“%”运算均是调用子程序来完成,代码长、执行速度慢。通常,只要求是求2n方的余数,均可使用位操作的方法来代替。

    a=a%8;
可以改为:
    a=a&7;

  乘除法:既使是在没有内置硬件乘法器的AVR单片机中,乘法运算的子程序比平方运算的子程序代码短,执行速度快。用移位的方法得到代码比调用乘除法子程序生成的代码效率高。实际上,只要是乘以或除以一个整数,均可以用移位的方法得到结果;

    a=pow(a, 2);
可以改为:
    a=a*a;

移位:

    a=a*9
可以改为:
    a=(a<<3)+a

  复合赋值表达式(如a-=1及a+=1等)都能够生成高质量的程序代码。


九,成员布局

(1)按数据类型的长度排序 ;把结构体的成员按照它们的类型长度排序,声明成员时把长的类型放在短的前面。编译器要求把长型数据类型存放在偶数地址边界。在申明一个复杂的数据类型 (既有多字节数据又有单字节数据) 时,应该首先存放多字节数据,然后再存放单字节数据,这样可以避免内存的空洞。

(2)把结构体填充成最长类型长度的整倍数 :把结构体填充成最长类型长度的整倍数。照这样,如果结构体的第一个成员对齐了,所有整个结构体自然也就对齐了。

(3)把频繁使用的指针型参数拷贝到本地变量 ;避免在函数中频繁使用指针型参数指向的值。因为编译器不知道指针之间是否存在冲突,所以指针型参数往往不能被编译器优化。这样数据不能被存放在寄存器中,而且明显地占用了内存带宽;用把指针型参数保存到本地变量。否则,请在函数一开始把指针指向的数据保存到本地变量。如果需要的话,在函数结束前拷贝回去。

void isqrt(unsigned long a, unsigned long* q, unsigned long* r)
{
  unsigned long qq, rr; //用两个变量;
  qq = a;
  if (a > 0)
  {
    while (qq > (rr = a / qq))
    {
      qq = (qq + rr) >> 1;
    }
  }
  rr = a - qq * qq;
  *q = qq;
  *r = rr;
}


十,register变量

  在声明局部变量的时候可以使用register关键字。这就使得编译器把变量放入一个多用途的寄存器中,而不是在堆栈中,合理使用这种方法可以提高执行速度。函数调用越是频繁,越是可能提高代码的速度。

  在最内层循环避免使用全局变量和静态变量,除非你能确定它在循环周期中不会动态变化,大多数编译器优化变量都只有一个办法,就是将他们置成寄存器变量,而对于动态变量,它们干脆放弃对整个表达式的优化。尽量避免把一个变量地址传递给另一个函数,虽然这个还很常用。C语言的编译器们总是先假定每一个函数的变量都是内部变量,这是由它的机制决定的,在这种情况下,它们的优化完成得最好。但是,一旦一个变量有可能被别的函数改变,这帮兄弟就再也不敢把变量放到寄存器里了,严重影响速度。看例子:

a = b();
c(&d);

  因为d的地址被c函数使用,有可能被改变,编译器不敢把它长时间的放在寄存器里,一旦运行到c(&d),编译器就把它放回内存,如果在循环里,会造成N次频繁的在内存和寄存器之间读写d的动作;

1、register修饰符暗示编译程序相应的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中,以加快其存储速度。例如下面的内存块拷贝代码,

/* Procedure for the assignment of structures, */
/* if the C compiler doesn't support this feature */
  #ifdef NOSTRUCTASSIGN
  memcpy (d, s, l)
{
    register char *d;
  register char *s;
  register int i;
  while (i--)
  *d++ = *s++;
  }
#endif

但是使用register修饰符有几点限制:

(1)register变量必须是能被CPU所接受的类型。
这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。
(2)因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。
(3)只有局部自动变量和形式参数可以作为寄存器变量,其它(如全局变量)不行。
在调用一个函数时占用一些寄存器以存放寄存器变量的值,函数调用结束后释放寄存器。此后,在调用另外一个函数时又可以利用这些寄存器来存放该函数的寄存器变量。
(4)局部静态变量不能定义为寄存器变量。不能写成:register static int a, b, c;
(5)由于寄存器的数量有限(不同的cpu寄存器数目不一),不能定义任意多个寄存器变量,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c/c++代码性能效率 的相关文章

随机推荐

  • 00011__ARM和STM32的区别

    https blog csdn net qq 34385566 article details 79668280
  • linux中查看系统资源占用情况的命令

    size 61 large top size 主要参数 d xff1a 指定更新的间隔 xff0c 以秒计算 q xff1a 没有任何延迟的更新 如果使用者有超级用户 xff0c 则top命令将会以最高的优先序执行 c xff1a 显示进程
  • 关于PendSV异常和SVC异常

    这里先说什么是异常 xff0c 什么是中断 xff1f 请下这张图 颜色加深的表项为异常 xff0c 这些属于cm3内核自带的 其中 3 xff0c 2 xff0c 1异常的优先级固定 xff0c 是不可更改的 xff0c 其余的异常中断优
  • FreeRTOS学习4-任务创建和删除

    关于任务创建有3个函数 1 动态创建一个任务 可以自动分配任务堆栈和TCB FreeRTOSConfig h中 xff0c 需要定义 define configSUPPORT DYNAMIC ALLOCATION 1 支持动态内存申请 Ba
  • java里 equals和== 区别

    1 java中equals和 61 61 的区别 值类型是存储在内存中的堆栈 xff08 简称栈 xff09 xff0c 而引用类型的变量在栈中仅仅是存储引用类型变量的地址 xff0c 而其本身则存储在堆中 2 61 61 操作比较的是两个
  • VRPTW建模与求解—基于粒子群算法

    VRPTW建模与求解 基于粒子群算法 1 VRPTW简要描述 VRPTW xff08 Vehicle Routing Problem with Time Windows xff09 是指在经典VRP的前提上 xff0c 给每个客户增添时间窗
  • 伽马分布,指数分布,泊松分布的关系 -转自简书

    原文链接 xff1a https www jianshu com p 6ee90ba47b4a 伽马分布 xff0c 指数分布 xff0c 泊松分布的关系 thinkando 关注 2018 09 25 21 13 字数 714 阅读 29
  • 双轴驱动步进电机云台二自由度单片机控制程序PTU57

    高精度云台由两个电机驱动 xff0c 可控制方位角和高度角 xff0c 具有两自由度的机械电子设备 可用于机器视觉 摄影摄像 监控安防 天文观测 雷达扫描 DIY雕刻机 转盘转台 智能机械手臂 双轴跟踪太阳能定日镜等各类应用高精度云台的场合
  • php使用curl获取需要认证的https请求

    lt php php使用curl获取需要认证的https请求的方法 url 61 34 XXXXXX 34 arr header 61 34 Accept application json 34 arr header 61 34 Autho
  • i-vector本质剖析

    1 i vector的由来 基于因子分析理论 xff0c 句子h的超向量可以描述成 其中为ubm模型的均值超向量 xff0c 即为i vector 2 i vector的计算 2 1 T矩阵的估计 为句子h的观察特征 xff0c 可以对应于
  • C++程序设计基础实验-实验七 多态性

    实验七多态性 一 实验目的 掌握运算符重载的方法 xff1b 掌握使用虚函数的继承实现动态多态性 掌握纯虚函数及抽象类的使用 二 实验内容 设计复数类Complex xff08 请参照教材例题8 1的设计 xff09 xff0c 实现运算符
  • g2o_a_general_framework_for_graph_optimaization

    g2o A General Framework for Graph Optimization NONLINEAR GRAPH OPTIMIZATION USING LEAST SQUARES 机器人和计算机视觉中的许多问题都可以用下列方程的
  • P5644 [PKUWC2018]猎人杀

    P5644 PKUWC2018 猎人杀 题目大意 一开始有 n n n 个猎人 xff0c 第 i i i 个猎人有仇恨度
  • 【Linux基础系列之】platform虚拟总线

    linux当中大多数的设备都是以paltform虚拟总线挂载上去的 xff0c 这里以kernel drivers net dm9000 c为例子分析一下 xff0c platform设备挂在过程 xff1b xff08 1 xff09 d
  • ARMv8-AArch64简述

    ARMv8是ARM版本升级以来最大的一次改变 xff0c ARMv8的架构继承以往ARMv7与之前处理器技术的基础 xff0c 除了现有的16 32bit的Thumb2指令支持外 xff0c 也向前兼容现有的A32 ARM 32bit 指令
  • ARMv8-AArch64寄存器和指令集

    xff08 一 xff09 简述 AArch拥有31个通用寄存器 xff0c 系统运行在64位状态下的时候名字叫Xn xff0c 运行在32位的时候就叫Wn xff1b AArch32与AArch64寄存器对应关系 xff1a xff08
  • ION框架学习(一)

    第一章介绍 xff1a ION的框架和buffer的分配 xff1b 第二章介绍 xff1a 如何使用ION buffer xff1b ION是google在Android4 0 为了解决内存碎片管理而引入的通用内存管理器 用来支持不同的内
  • 高通Camera 驱动调试要点(一)

    本文主要介绍QCOM camera调试的重要参数 xff1b xff08 1 xff09 Lane assign 和lane mask 现在摄像头基本都是mipi接口类型 xff0c 因为前后摄都对应到平台这边不同的mipi接口 xff0c
  • 高通Camera 驱动调试要点(二)

    这篇文章主要介绍数据流这边Camera ISP这块所遇到的问题 xff0c 主要介绍bus overflow和sof freeze xff1b xff08 一 xff09 bus overflow 摄像头传感器时钟通道 即 MIPI DDR
  • c/c++代码性能效率

    一 尽量减少值传递 xff0c 多用引用来传递参数 boolCompare xff08 span class hljs keyword string span s1 span class hljs keyword string span s