读《代码优化:有效使用内存》

来源:互联网 发布:声音模拟的软件 编辑:程序博客网 时间:2024/05/16 20:28

http://dearymz.blog.163.com/blog/static/20565742009327104921818/

最深感受:提前优化是一切罪恶的根源!优化只在有必要和有需求的情况下进行。优化只针对热点进行。

前言 优化概述
优化要注意的问题:
  优化应该尽可能保持与硬件无关,尽量的考虑操作系统的移植性
  优化不应该使开发过程(包括测试)的劳动强度增加15%以上
  优化算法必须使程序性能的提高不少于20%,否则和风险比起来有点不值得采用
  优化应该使代码修改起来毫不费力。代码的可维护性不可忽视
 
优化规则:
  在进行代码优化之前,先要有一个同一代码的可靠的、非优化的版本
  应利用算法优化错事,而不是通过提升系统的特性来获取最大限度的性能。尽量采用更优化的算法,而不是去盲目优化
  不要将代码优化与汇编语言实现混为一谈。应尽量在高级语言的范围内进行优化,在非极端情况下不要采用汇编进行重写
  在试图用汇编语言重写程序之前,先查看一下编译器生成的汇编代码并估算它的效率
  如果编译器生成的汇编语言列表虽然显得很不错,但是程序运行起来仍然很慢,就可以考虑将它加载到一个反汇编工具中去,这样看的更精确
  如果可用的处理器指令能够实现比较高效的算法,那么就用不着去管编译器而可以着手实现汇编语言代码。不过,这种情况非常少见,编译器和库实现通常会做的更好
  在开发汇编语言代码时,不管存在什么样的干扰,都应该给出一个精巧而高效的方案。程序代码中尽量避免使用内联汇编。汇编语言技巧仅仅用在软件版权保护方面才是合理的
 
讹传平反(读音:e 二声):
  编译器并不会替用户完成所有的优化任务,但应该尽量的协助编译器去做这些事情
  并非只有汇编才能得到高性能,高级语言一样可以做到。手工优化后的代码相对编译器实现一般只能提高10%~20%,但附加的困难和工作强度非常大
  除了Intel编译器外的编译器都不能做到对现有处理器体系最优的代码,针对特定平台时可以考虑这些特殊的编译器和手工优化。但通用目的下往往都是失败的。
  每种处理器体系结构都有自己的优缺点,并非高性能就要使用PowerPC
 
第一章 程序剖分
  消除并不“最热”的热点并不能使程序的性能得到实质性的提升
  从整体上考虑程序的运行时性能,而不是局部
  对于“漂浮不定的”热点,应该缺点它们在最好、平均、最坏情况下对应的执行时间
  在开始优化之前,一定要确信执行次数大的可以掩盖最初的加载开销。只有足够的运行次数才能测出准确的热点
  多次运行中的最快的一次是受各种负面影响最小的一次
  程序的优化是多层次的,善于观察善于发现总能得到更好的结果
 
第二章 RAM子系统
  4重展开循环一般可以获得更好的效果
  C语言预处理器不支持循环宏,不具备高效循环展开能力,但宏汇编可以做到
  消除指令和数据的相关性,使紧邻的指令尽量不要耦合,这可以给CPU提供并行的可能性
  CPU和RAM进行数据交换的单位不是字节而是突发周期内的一个数据包(随处理器不同可能是32字节到128字节)
  并发读取两个相邻的内存单元实际可能只会发生一次RAM调用
  内存中的数据应该尽可能的紧密存放在一起
  使用BYTE并不会比用DWORD取得更好的内存读写效率
  对于非字节对齐的数据操作时间可能需要两个突发周期而使时间加倍
  x86之外的其他CPU(如x64)在访问对字节对齐数据时会产生一个异常,这通常会交由OS去做善后来完成重新的读写。非字节对齐会严重影响性能
  只在必要时才访问内存
  memcpy经典实现的while(count--) *dst++=*src 至少存在三方面的性能问题:读写事务出现重叠,单元的并行化处理水平低,在同一个DRAM板块中存在流交叉的可能性。借助中间缓冲区可以环节这些问题
  memcpy无法处理内存交叠问题,memmove可以搞定
  内存系统是针对向前操作进行优化的,从后往前的地址访问是低效的,避免逆向迭代
  C格式字符串最大的性能瓶颈就在strlen上,这会引发O(n)的逐字节数据流扫描
 
第三章 高速缓存子系统
  最快的RAM是SRAM,Cache是用SRAM实现的
  Cache的作用:保证对频繁使用数据的高速存取;从主存预加载数据(现代CPU是主动预加载的);实现延迟的数据写操作
  L1通常是和CPU同频的,数据处理仅需一个时钟
  L2一般是半频工作,需10个CPU时钟才可完成数据操作
  RAM操作需100左右时钟才可完成
  任务切换对CPU来说相当于数百万时钟
  现在Cache的行尺寸一般是64字节,也就是说Cache对数据的容纳是以64字节为基本单位的
  代码Cache的未命中比数据Cache的未命中具有更加惨重的代价。
  所有频繁执行的循环最好都能放进代码L1中,至少应能放进代码L2中,超大的循环是性能杀手
  当数据操作在Cache行尺寸范围内时,即使没有字节对齐也只需要一个时钟,但跨界操作需要6~12个时钟的代价
  BYTE数据永远不会发生跨界,WORD以2字节对齐不会发生跨界,DWORD以4字节对齐不会发生跨界
  编译器对静态变量和全局变量会强制采用字节对齐,而忽视编译选项和pack标记;栈变量将被4字节对齐;堆变量16字节对齐;结构体内的对齐按照数据类型来实施
  循环计数器或其他频繁使用的变量没有对齐并产生跨界时对性能的损伤非常严重
  因流数据从RAM加载的时间大大超过了Cache行跨界所消耗的时间,对齐流数据对性能没有太大的意义
  若所有的数据都以4字节对齐,这会很大程度上利用CPU的并行功能
  如果数据处理量很小的程序执行起来特别慢,不妨检查一下是否存在Cache的行冲突
  高速缓存优化的任务是不能靠编译器来完成的,程序员必须自己承担这个任务
  后台那些经常处于休眠状态的程序是不会影响前台业务密集程序的效率的
  不针对具体处理器模型优化程序通常是不可能获取最大限度的性能的
  当前市面上的OS并没有为应用软件提供DMA控制接口,不可能用DMA进行memcpy。普通计算机并没有实现内存到内存的复制模型
  拷贝内存时以4k为缓冲单位,16字节对齐可以获得很大的性能提升
  memset在8字节对齐时会获得很大的性能提升
 
第四章 机器优化
  目前的编译器不太擅长于代数式的化简
  移位运算需要1个时钟,乘法运算一般需要4个时钟,除法和mod运算需要40个以上的时钟
  紧密的switch会被编译为跳转表,稀疏的switch会被编译为判定树进行二分查找
  对于各种标准的函数调用方式,编译器没有权限动手脚,会严格按照标准进行优化
  如果函数类型没有显式给出,编译器有权决定如果传递参数
  VC和BCB只有在显式指定了__fastcall的时候才会采用寄存器传参,但this默认就是通过寄存器传递的。VC只有两个通用寄存器用于传参,BCB有三个
  如果手工采用的汇编技巧不是最新的,那会被优化器鄙视的
  汇编可以很好的利用多媒体数据并行处理新指令的优越性,对高性能的数字图形库很重要
  一般将程序转变为汇编会使尺寸减小一半
  汇编语言还活着,并且将永远活下去!

0 0
原创粉丝点击