4. 性能与可用性
性能更好的软件产品是为用户节省时间。对许多计算机用户时间是宝贵的资源,在慢的、难用的、不兼容或易出错的软件上浪费了许多时间。所有这些问题是可用性问题,我相信应该在更宽广的可用性角度看待软件性能。
可用性没有手册,但我相信这里引起软件程序员注意软件有效使用最常见的障碍是必要的。更多这个话题,参考WikiBooks上我的电子书《Usability for Nerds》。
以下列表指出软件使用者的挫败感与时间浪费的某些典型源头,以及软件开发者应该注意的重要可用性问题。
- 大运行时框架。.NET框架与Java虚拟机是典型的,比所运行程序消耗资源多得多的框架。这样的框架是资源问题、兼容性问题的常见源头,在安装框架本身期间、在安装运行在该框架下的程序期间、在程序启动期间,以及程序运行时刻,它们浪费了大量的时间。为什么使用这样的运行时框架的主要原因是为了跨平台的可移植性。不幸的是,跨平台兼容性不总是如预期那么好。我相信通过更好地标准化编程语言、操作系统与API,可以更高效地实现可移植性。
- 内存切换。软件开发者使用的计算机通常比终端用户更强大,有更多的RAM。因此,开发者可能不会看到过多的内存切换,以及导致对终端用户耗资源应用性能差的其他资源问题
- 安装问题。程序的安装与卸载过程应该标准化,并通过操作系统完成,而不是独立的安装工具。
- 自动更新。如果网络不稳定或者如果新版本导致旧版本中没有的问题,软件的自动更新会导致问题。更新机制通常通过让人烦恼不安、宣称请安装这个重要更新,甚至在用户专心于重要工作时告知重启计算机的弹出消息,来打扰用户。更新机制不应该打扰用户,而是显示一个独立的图标表示有可用的更新,或者在计算机重启时自动更新。软件发布者通常滥用更新机制,来对他们的软件新版本大广告。这让用户讨厌。
- 兼容性问题。所有的软件都应该对不同的平台、不同的屏幕分辨率、不同的系统颜色设置以及不同的用户服务权限,进行测试。软件应该使用标准API调用,而不是黑客手段与直接硬件访问。应该使用有效的协议以及标准化的文件格式。应该以不同的浏览器、不同的平台、不同的屏幕分辨率等测试Web系统。应该遵守访问权限指引。
- 拷贝保护。某些拷贝保护方案基于违反或绕过操作系统标准的黑客手段。这样的方案经常是兼容性问题以及系统宕机的源头。许多拷贝保护方案基于硬件识别。在硬件更新时,这样的方案会导致问题。大多数拷贝保护方案让用户讨厌,阻止了合法的备份,却没有有效地防止非法拷贝。拷贝保护方案的好处应该以可用性问题以及必要的支持来衡量。
- 硬件更新。硬盘或其他硬件的改变通常要求重新安装所有的软件,并且用户设置丢失。重新安装花费一整天或更多时间并不罕见。许多软件用于需要更好的支持特性,当前操作系统需要更好地支持硬盘拷贝。
- 安全。网络访问软件对病毒攻击以及其他滥用的脆弱性,对许多用户是极高的代价。防火墙、病毒扫描器与其他保护手段是兼容性问题以及系统崩溃最常见的部分原因。另外,病毒扫描器消耗最多的时间并不罕见。作为操作系统一部分的安全软件通常比第三方安全软件更可靠。
- 后台服务。运行在后台的许多服务对用户是不必要的,是资源的浪费。考虑仅在用户激活时运行服务。
- 特性激增。出于市场原因,软件某个新版本添加新功能很常见。这会导致软件更慢,或要求更多资源,即使用户从不使用新特性。
- 认真对待用户反馈。用户抱怨应该视为关于bug、兼容性问题、可用性问题及期望的新特性的,一个有价值的信息来源。应该以系统的方式处理用户反馈,确保信息被合理使用。用户应该得到关于问题调查以及解决计划的反馈。补丁应该容易从网上获取。
5. 选择最优算法
在你希望优化一段CPU密集软件时,第一级要做的事情是找出最好的算法。算法的选择对诸如排序、查找及算术计算等任务非常重要。在这样的情形里,选择最好的算法,比优化想到的第一个算法,获益要多得多。在某些情形里,你可能必须测试几个不同的算法,以找出在测试数据的一个典型集合上工作得最好的那个。
话虽如此,我必须提醒过度拟合。如果一个简单算法可以足够地完成工作,不要使用一个先进且复杂的算法。例如,某些程序员对即便最小的数据链表也使用哈希表。对非常大的数据集,哈希表可以极大地提升改善时间,但对二分查找或甚至线性查找都足够快的小链表,没有理由使用它。哈希表增加了程序的大小,以及数据文件的大小。如果瓶颈是文件访问或缓存访问,而不是CPU时间,这实际上降低了速度。复杂算法的另一个坏处是,它使得程序的开发代价更高,更易出错。
不同目的各种算法的讨论超出本手册范畴。对诸如排序与查找的标准任务,可以参考算法的一般性文献,对更复杂的数学任务,可以参考特定的文献。
在你开始写代码之前,你可以考虑别人是否在你之前做了这个工作。用于许多标准任务的优化函数库可以从若干来源获得。例如,Boost包含了用于许多常见用途、经过良好测试的库(www.boost.org)。“Intel Math Kernel Library”包含许多用于常见算术计算的函数,包括线性代数与统计,而“Intel Performance Primitives”库包含许多用于视频与音频处理、信号处理、数据压缩与编解码的函数(www.intel.com)。如果你正在使用一个Intel函数库,那么确保它在非Intel处理器上工作良好,如第133页所述。
在开始写代码前选择最优算法,通常说比做容易。许多程序员发现,仅在他们把整个软件项目放在一起并测试之后,才有更聪明的方式做事情。通过测试、分析程序性能以及对瓶颈的研究所获得的洞察,会导致对程序整个结构更好的理解。这个新洞察会导致完全重新设计该程序,例如当你发现有更聪明的方式组织数据时。
完全重新设计已经工作的程序,当然是一项可观的工作,但它会是相当好的投资。重新设计不仅改进了性能,它还可能导致程序更容易维护、结构化更好。你花在重新程序的时间可实际上能少于你解决原来设计不良程序的问题的时间。
6. 开发过程
关于使用哪个软件开发过程及软件工程原则,有大量的争论。我不准备建议任何特定的模型。相反,关于开发过程会如何影响最终产品的性能,我将给出几个意见。
在计划好的阶段里,对数据结构、数据流及算法进行细致分析,预测哪些资源是最关键的,是好的。不过,在早期计划阶段太多未知因素,不容易获得程序详细的概观。在后一种情形里,你可以视软件开发工作为一个学习的过程,其中主要的反馈来自测试。这里,你应该为几次迭代重新设计做好准备。
某些软件开发模型有在软件的逻辑架构中要求几层抽象的严格形式体系。你应该了解,对这样一个形式体系,有内在的性能代价。将软件分解为过多的独立抽象层是性能下降的常见原因。
因为大多数开发方法本质上是增量或迭代的,有保存每个中间版本备份的一个策略是重要的。对一个人的项目,保留每个版本的一个zip文件就足够了。对团队项目,建议使用版本控制工具。