12. 引用计数
1. 内存讹误是软件设计的主要难题之一,分配好的内存在系统内部以指针传递的方式流通。这个会在糟糕的软件系统里造成两个主要问题:
(1). 内存泄漏使得内存消耗殆尽。程序崩溃。
(2). 过早的删除操作,导致灾难性故障。
2. 垃圾回收机制——引用计数,基本原理是将要销毁的对象从客户端代码转移到对象本身,对象跟踪记录自身被引用的次数,达到指定次数自动销毁。
3. 测试案例:
总结:引用计数和执行速度之间的关系与上下文密切联系,取决因素有以下几点:
(1). 目标对象的资源消耗集中在哪些方面?
(2). 分配(释放)目标对象所需要的使用资源需要多少代价?因为从堆中分配1字节的空间需要执行上百条指令,释放时也一样。
(3). 可能有多少对象共享目标对象的单个实例?通过赋值操作符合拷贝函数提高共享率。
(4). 创建(销毁)第一个(最后一个)目标对象引用的频率有多高?
4. 要点:
(1). 目标对象是很大的资源消费者。
(2). 资源分配和释放的代价很高。
(3). 高度共享,由于使用赋值操作符和拷贝构造函数,引用计数的性能可能会很高。
(4). 创建和销毁引用的代价低廉。
反之,应该跳出引用计数转为使用更加有效的简单非计数对象。
13. 编码优化
1. 缓存主要是用来存储使用频繁而且代价高昂的计算结果,这样避免对这些结果重复计算。循环内对常用表达式求值是一种常见的低性能问题。
2. 当缓存某个计算结果时,如果采用预先计算,那么甚至连这一次计算都可以避免了。将预先计算放在影响性能的关键路径之外(例如初始化阶段),就可以避免在性能的关键路径上进行代价高昂的运算。
3. 通过降低代码的灵活性,也可以提高性能。例如IP地址设定为256字节大小消除了代价高昂的new()和delete()。
4. 80-20法则适用场景:80%的代码只遍历20%的代码;80%的程序运行时间消耗在20%的函数上。
5. 为一个最终可能不需要的计算付出性能代价显然不是明智之举。延迟计算原则是建议我们推迟对象的定义,直到要使用该对象时再定义。没有必要为了不一定使用到构造函数和析构函数付出代价。
6. 在特定的RISC体系结构中,若数据位于数据缓存中,那么访问它需要消耗一个CPU周期;如果在主存中(缓存失败),则需要8个CPU周期;如果位于硬盘上(页面错误),则需要400,000个CPU周期。缓存成功、缓存失败和页面错误之前的速度相差多个数量级。我们要做的是尽量使下一个要访问的数据内存位置与当前数据的内存位置相邻,这样更有可能缓存成功。
7. 要点:
(1). 尽量减少内存管理调用的数量。
(2). 有时候可以牺牲其他不经常出现的场景来提高典型输入的处理速度。
(3). 缓存、RAM和磁盘访问的速度差异很明显。应该多编写缓存友好的代码。
14. 设计优化
1. 在软件灵活性和性能之间有一个基本的平衡。特定的条件和简化的假设是常见的获取高性能的方法。
2. 如果自己的程序需要不断重复地使用一个对象来获取一个相关的结果,那么更好的做法是将这个结果作为对象数据成员保存起来,并在将来的计算中使用。
3. 高效的算法和所使用的数据结构的效率是软件性能的重要因素。
4. 清理失效代码,原因是失效代码增加了可执行文件的规模,增大了内存占用还有产生了源码冗余,后期难以维护、拓展。建议清除源代码中的失效代码和僵尸代码。
5. 要点:
(1). 性能的损失通常在过于灵活上。
(2). 对于软件的高效性,高效算法和数据结构是必要条件。
(3). 定期清理无效代码。