当前位置: 首页 > 科技观察

浅谈软件开发的性能提升

时间:2023-03-22 14:23:54 科技观察

背景运行操作软件时,一个操作执行的太慢。首先要分清是IO操作密集型问题还是CPU相关的计算密集型问题引起的。软件的性能优化,无论是从编码规范还是工程项目实践上,都有很多方向是我们作为开发者需要关注的。性能优化的目的是让程序的执行功能更加高效,但同时也不应该丧失程序的可维护性和可扩展性。性能优化是一门实验科学,往往是通过不断的迭代来进行的。每次优化方案实施后,需要对比优化前后程序的性能,验证优化方案的可行性。下面主要从C和C++语言入手,进行一些代码性能优化和分析,帮助开发性能比较高的软件。理论基础影响软件程序性能架构的因素主要有两个:硬件和软件。影响硬性能的因素包括:处理计算机体系结构下存储系统层次结构的排列顺序:cpu处理器中允许多条指令不按程序指定顺序分别发送给相应电路单元处理的技术。在cpu处理器中,指令被分解为多个步骤,不同指令的步骤相互重叠,使多条指令并行处理,从而加快程序运行过程,缩短程序执行时间。cpu允许同时获取多个任务,并同时执行这些任务。并行的效率从代码层面强烈依赖多进程或多线程代码,从硬件角度更多依赖多核cpu。每个任务分配给每个处理器独立完成。在同一时间点,任务必须同时运行。并行是指不同的代码片段同时在不同的物理处理器上执行。并发:将任务分配给处理器,在不同的时间点进行处理。在同一时间点,任务不会并发运行。其他方面:内存大小、硬盘大小、网络中的网卡、网速。影响软件性能的主要因素有:系统函数调用开销编译器优化语言抽象软件系统函数调用:如open、read、fread、write、close、mmap、sbrk、time、gettimeofday等系统函数(因为需要通过系统调用与内核交互)。编译器优化:在没有同步原语(包括:互斥操作、内存屏障、原子操作等)的情况下,编译器一般可以针对程序的性能自由调整当前线程的执行顺序。语言抽象(表示为lexical-level和lexical-levelabstraction):C和C++语言的中间文件是obj文件,在栈上分配sizeof(obj)字节空间,它们的时间复杂度为0(1)与C语言C++面向对象中的类机制相比,涉及类初始化时的构造函数调用,类结束时的析构函数,会给程序带来一定的性能影响。编译器优化软件的开发离不开作为基础的编译器工具,编译器工具的合理使用也能为程序性能的提升提供助力。从编译器说起优化的一个小思路。1.在没有同步原语(互斥锁操作、内存屏障、原子操作)的情况下,编译器可以为了性能自由调整执行顺序,而不改变当前线程的结果。2.在编译器中,会自动进行语句的等价转换。例如:x=a;y=2;可以自动转换成y=2;x=一个;再入x=y+1;y=x+2可以等价转化为t=y;y+=3;x=t+1。3.在编译器中,局部变量可能会被完全淘汰。4.全局变量只保证在下一个同步点到来之前写回内存。5.Volatie语句会禁止编译器进行相关的优化。6.在编译器中,可以使用__attribute__((noinline))来防止意外内联。循环中的优化程序使用循环语句,在某些情况下会大大增加计算机中CPU的计算时间和效率。因此,在程序的性能优化中,循环语句是一个非常大的技术点,需要重点设计考虑。下面列出几种循环语句的优化思路。将重复执行的不需要的代码提取出来,在循环外执行。考虑使用宏定义来替换经常调用的函数的函数。C++为了优化引入了inline,但是有时候inline在函数体比较长的时候不起作用,所以可以考虑将频繁调用的函数重写为宏定义。一个循环中多个不相关的处理可以分成多个循环语句,可以更好的提高缓存命中率,在某些场景下可以显着提升性能。减少循环体中的跳转,尽量让流程按顺序执行,去掉循环中不变的代码。对象参数的优化如果不修改对象,建议使用constobj&方法。如果需要修改对象,建议使用obj&方法。如果需要对对象的新副本进行操作,建议直接使用obj方法。String接口的优化不建议使用constString&(除非调用方保证有现成的String对象);如果不需要修改字符串的内容,可以使用string_view或者constchar*;如果只是在函数内部修改字符串的内容,可以直接使用String方法;如果需要修改调用者字符串的内容,推荐使用string&方法。函数和虚函数的优化函数的调用使处理器跳转到另一个代码地址再回来。这个过程一般需要4个时钟周期。大多数情况下,处理器会将函数调用、返回等指令一起执行,以节省运行时间。.函数的参数入栈需要额外的时间(包括栈帧的建立,保存和恢复寄存器,可能还有异常信息等)。下面列出了一些改进与功能相关的性能的想法。1、避免过多使用不必要的函数,尤其是在底层循环中,尽量将代码集中在一个函数中。这似乎与良好的编码习惯(一个函数不要超过80行)有冲突,我们应该知道什么时候关注函数的这些优化,而不是一上来就让代码的可读性和可维护性变差。2.有些内联函数可以直接用函数体代替调用函数的地方。inline是给编译器的建议,如果是inline性能不好。一般当函数比较小或者只有一个地方被调用时,inline的效果会相对好一些。3、减少函数的间接调用,比如偏向静态链接而不是动态链接,尽量少用或不用多重继承、虚拟继承等风格。4.迭代优于递归。5、用函数代替define,避免多次求值。宏的其他缺点:不能重载和限制范围。6、减少虚函数的使用,尽量使用模板来代替虚函数的使用。7.类的使用,在使用构造函数和析构函数的同时尽可能简单,杜绝不必要的构造函数和析构函数的重复使用。8、使用类对象时,复制对象的开销大。最好选择按引用传递而不是按值传递。运算表达式优化在运算过程中,尝试将常量合并在一起。比如当a*x*b==(a*b)*x时,double类型在有硬件浮点单元的机器上比float效率更高,但是一般来说,单精度和double的计算性能精度是一样的。在除法和取余运算的情况下,无符号整数(无符号类型)将比有符号整数(符合类型)更快。在除法中,除以常数比除以变量效率更高,因为它可以在编译时优化,尤其是当常数可以表示为2^n时。++i和i++的性能是一样的,但是在不同的上下文中,它们的效果是不一样的,比如array[i++]比arry[++i]有更好的性能;当依赖自增结果时,++i性能更好,比如a=++b,a和b可以复用同一个寄存器。浮点数除法比乘法慢很多,所以可以用乘法代替除法,这样可以提高代码性能。内存优化程序在运行时,占用的内存越少,其运行效率越快,也意味着程序的运行性能越好。那么,如果对这段内存进行优化,程序能否达到更好的性能呢?下面分析几种内存优化方案。该程序最大限度地减少了对内存管理器的调用次数。减少内存读写操作,尤其是减少内存写入次数,内存访问和读取操作尽量顺序进行。一起使用的功能存储在一起。函数通常按照源代码的顺序存储。如果同时调用函数A、B、C,尽量让ABC的声明按照这个顺序。一起使用的变量存储在一起。使用结构体和对象来定义变量,并通过局部变量声明。这些都是一些不错的选择。动态内存分配、STL容器和字符串是一些容易缓存不友好的场景。尽量不要在核心代码中使用它们。使用。算法优化在程序开发过程中,可以根据数据集的特点选择更高的数据结构和算法策略,这就需要开发人员对数据结构和算法的空间复杂度和时间复杂度有清晰的认识。程序中的算法会极大地影响程序的性能,因此选择一个合适且高效的算法非常重要。多线程优化多线程加锁和竞争是影响程序性能的杀手;在多线程中,如果可以使用atomic,就不要使用mutex;如果读多于写,使用读写锁(shared_mutex)代替排他锁(mutex);使用线程局部(thread_local)变量。总结程序的性能优化,不仅要从编译器、语言特点、编码习惯、算法选择、程序架构设计等方面入手,不断提升程序的性能,从而提升用户体验。最后从项目中整理出几个可以优化的思路。1.删??除项目中的冗余代码。2.字符串操作优化。3.减少内存分配和释放操作,比如可以使用内存池。4.减少不必要的互斥操作。5、根据性能需求选择数据结构。6.延迟工作,按需执行。7.减少跨进程调用。8.使用高性能函数库。9.可以使用智能指针代替指针的使用。10、优化动态库文件的加载,尽量避免不必要的IO操作。最后推荐一个非常好的在线编码平台(可以自动将代码转为汇编代码,支持多平台汇编代码的转换):COMPILEREXPLORER;以下是平台链接https://godbolt.org/本文转载自微信公众号“TrailSafety”,可通过以下二维码关注。转载本文请联系小刀安全公众号。