代码调优法则

2024-06-23 15:32
文章标签 代码 法则 调优

本文主要是介绍代码调优法则,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《编程珠玑》第二版快看完了,发现附录D作者记录了一些代码调优法则,本人总结了一下,发出来给大家分享分享,顺便自己也做个笔记。(注:这篇文章是用那个markdown写的,第一次用markdown发布博客,有点小激动。)

代码调优法则

空间换时间法则

  • 修改数据结构。为了减少数据上的常见运算所需要的时间,我们通常可以在数据结构中增加额外的信息,或者修改数据结构中的信息使之更易访问。
    例如:std::vector中的empty和size成员函数之类的。
  • 存储预先计算好的结果。对于开销较大的函数,可以只计算一次,然后将计算结果存储起来以减少开销。以后再需要该函数时,可以直接查表而不需要重新计算。
    例如:读取文件中的数据时不用每次都去读取,第一次读取出来时保存数据即可。
  • 高速缓存。最经常访问的数据,其访问开销应该是最小的。
  • 懒惰求值。除非需要,否则不对任何一项求值。这一策略可以避免对不必要的项求值。
    例如:类模板中只对使用到的成员函数实例化。

时间换空间法则

  • 堆积。密集存储表示可以通过增加存储和检索数据所需的时间来减少存储开销。
    例如:使用数组来表示稀疏矩阵。
  • 解释程序。使用解释程序通常可以减少表示程序所需的空间,在解释程序中常见的操作序列以一种紧凑的方式表示。

循环法则

  • 将代码移出循环。与其在循环的每次迭代时都执行一次某种计算,不如将其移到循环体外,只计算一次。
  • 合并测试条件。高效的内循环应该包含尽量少的测试条件,最好只有一个。因此,程序员应尽量用一些退出条件来模拟循环的其他退出条件。
    例如:哨兵是该法则的常见应用,在数据结构的边界上放一个哨兵以减少测试是否已搜索结束的开销。
  • 循环展开。循环展开可以减少修改循环下标的开销,对于避免管道延迟、减少分支以及增加指令级的并行性也都很有帮助。
  • 删除赋值。如果内循环中很多开销来自普通的赋值,通常可以通过重复代码并修改变量的使用来删除这些赋值。
  • 消除无条件分支。快速的循环中不应该包含无条件分支。通过“旋转”循环,在底部加上一个条件分支,能够消除循环结束处的无条件分支。该操作通常由编译器优化完成。
  • 循环合并。如果两个相邻的循环作用在同一组元素上,那么可以合并其运算部分,仅使用一组循环控制操作。

逻辑法则

  • 利用等价的代数表达式。如果逻辑表达式的求值开销太大,就将其替换为开销较小的等价代数表达式。
  • 短路单调函数。一旦找到了循环的目的就可以退出循环了。
  • 对测试条件重新排序。在组织逻辑测试的时候,应该将低开销的、经常成功的测试放在高开销的、很少成功的测试前面。
  • 预先计算逻辑函数。在比较小的有限域上,可以用查表来取代逻辑函数。
  • 消除布尔变量。我们可以用if-else语句来取代对布尔变量的赋值,从而取消程序中的布尔变量。

过程法则

  • 打破函数层次。对于(非递归地)调用自身的函数,通常可以通过将其改写为内联版本并固定传入的变量来缩短其运行时间。
  • 高效处理常见情况。应该使函数能正确处理所有情况,并能搞笑处理常见情况。
  • 协同程序。通常,使用协同例程能够将多趟算法转换为单趟算法。
  • 递归函数转换。递归函数的运行时间往往可以通过下面的转换来缩短:
    1.将递归重写为迭代,通过使用一个显式的程序栈将递归转化为迭代(如果函数仅包含一个对其自身的递归调用,那么就没有必要将返回地址存储在栈中);
    2.如果函数的最后一步是递归调用其自身,那么使用一个到其第一条语句的分支来替换该调用,这通常称为消除尾递归。该操作通常由编译器优化完成。
    3.解决小的子问题时,使用辅助过程通常比把问题的规模变为0或1更有效。例如:快速排序递归的规模小于50的时候不再递归,对整体数组使用一次插入排序比继续递归的速度更快。
  • 并行性。在底层硬件条件下,构建的程序应该尽可能多地挖掘并行性。

表达式法则

  • 编译时初始化。在程序执行之前,应该对使用到的变量初始化。
  • 利用等价的代数表达式。如果表达式的求值开销太大,就将其替换为开销较小的等价代数表达式。
    例如:使用if语句替换%取模运算。
  • 消除公共子表达式。如果两次对同一个表达式求值时,其所有变量都没有任何改动,那么我们可以用下面的方法避免第二次求值:存储第一次的计算结果并用其取代第二次求值。
    例如:第二次读取文件数据前检查文件的修改日期,若没有改变,则使用第一次读取的数据即可。
  • 成对计算。如果经常需要对两个类似的表达式一起求值,那么就应该建立一个新的过程,将它们成对求值。
  • 利用计算机字的并行性。用底层计算机体系结构的全部数据路径宽度来对高开销的表达式求值。

这篇关于代码调优法则的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1087553

相关文章

利用Python调试串口的示例代码

《利用Python调试串口的示例代码》在嵌入式开发、物联网设备调试过程中,串口通信是最基础的调试手段本文将带你用Python+ttkbootstrap打造一款高颜值、多功能的串口调试助手,需要的可以了... 目录概述:为什么需要专业的串口调试工具项目架构设计1.1 技术栈选型1.2 关键类说明1.3 线程模

Python Transformers库(NLP处理库)案例代码讲解

《PythonTransformers库(NLP处理库)案例代码讲解》本文介绍transformers库的全面讲解,包含基础知识、高级用法、案例代码及学习路径,内容经过组织,适合不同阶段的学习者,对... 目录一、基础知识1. Transformers 库简介2. 安装与环境配置3. 快速上手示例二、核心模

Java的栈与队列实现代码解析

《Java的栈与队列实现代码解析》栈是常见的线性数据结构,栈的特点是以先进后出的形式,后进先出,先进后出,分为栈底和栈顶,栈应用于内存的分配,表达式求值,存储临时的数据和方法的调用等,本文给大家介绍J... 目录栈的概念(Stack)栈的实现代码队列(Queue)模拟实现队列(双链表实现)循环队列(循环数组

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

使用Python实现全能手机虚拟键盘的示例代码

《使用Python实现全能手机虚拟键盘的示例代码》在数字化办公时代,你是否遇到过这样的场景:会议室投影电脑突然键盘失灵、躺在沙发上想远程控制书房电脑、或者需要给长辈远程协助操作?今天我要分享的Pyth... 目录一、项目概述:不止于键盘的远程控制方案1.1 创新价值1.2 技术栈全景二、需求实现步骤一、需求

Java中Date、LocalDate、LocalDateTime、LocalTime、时间戳之间的相互转换代码

《Java中Date、LocalDate、LocalDateTime、LocalTime、时间戳之间的相互转换代码》:本文主要介绍Java中日期时间转换的多种方法,包括将Date转换为LocalD... 目录一、Date转LocalDateTime二、Date转LocalDate三、LocalDateTim

jupyter代码块没有运行图标的解决方案

《jupyter代码块没有运行图标的解决方案》:本文主要介绍jupyter代码块没有运行图标的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录jupyter代码块没有运行图标的解决1.找到Jupyter notebook的系统配置文件2.这时候一般会搜索到

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La