进行实验
开发软件在某种意义上就是一项实验。你想让程序做一些事情,然后开始编程,最后观察
程序的运行结果是否与预想的一样。性能调优则是更有正式意义的实验。在开始性能调优
前,必须要有正确的代码,即在某种意义上可以完成我们所期待的处理的代码。你需要擦
亮眼睛审视这些代码,然后问自己:“为什么这些代码是热点?”为什么某个函数与程序
中的上百个函数不同,出现在了分析器的最差性能列表中的最前面?是这个函数浪费了很
多时间在冗余处理上吗?有其他更快的方法进行相同的计算吗?这个函数使用了紧缺的计
算机资源吗?是这个函数自身已经是非常快了,只不过它被调用了太多次,已经没有优化
的余地了吗?
你对于“为什么这些代码是热点”这个问题的回答构成了你要测试的假设。实验要对程序
的两种运行时间进行测量:一种是修改前的运行时间,一种是修改后的运行时间。如果后
者比前者短,那么实验验证了你的假设。
请注意这里的用词。实验并不需要证明任何事情。修改后的代码可能会因为某些原因运行
得更快或者更慢,但这些原因却与你修改的部分没有任何关系。比如:• 当你在测量运行时间时,计算机可能在接收邮件或是检查 Java 是否有版本• 在你重编译之前,一位同事刚刚签入了一个性能改善后的库;• 你的修改可能运行得更快,但是处理逻辑却是不正优秀的科学家是怀疑论者。他们总是对事物持有怀疑。如果没有出现所期待的实验结果,
或是实验结果太好了,不像是对的,那么怀疑论者会再进行一次实验或者质疑她的假设,
抑或检查是否有 bug。
优秀的科学家会接受新知识,即使这些知识与他们脑海中的知识相悖。。优秀的
科学家从不会停止学习。
记实验笔记:
优秀的优化人员(如同所有优秀的科学家)都会关心可重复性。这时实验室笔记本就可以
发挥作用了。为了验证猜想,优化人员在对代码进行一处或多处修改后,利用输入数据集
对代码进行性能测试,而测试则会在若干毫秒后结束。在与下次运行时间进行比较前一直
记着上次程序的运行时间,这事儿并不难。如果每次代码改善都是成功的,用脑袋记住就
足够了。
不过,开发人员的猜想可能会出错,这将导致最近一次的程序运行时间比上一次的更长。
这时,无数的疑问会充斥在开发人员的脑中。虽然 5 号测试的运行时间比 4 号长,但是它
比 3 号短吗?在进行 3 号测试时修改了哪些代码?两次测试间的速度差异是由其他因素造
成的,还是的确变快了?
如果每次的测试运行情况都被记录在案,那么就可以快速地重复实验,回答上述问题就会
变得很轻松了。否则,开发人员必须回过头去重新做一次实验来获取运行时间——前提是
他还记得应该修改哪些代码或是撤销哪些修改。如果测试运行很简单,开发人员的记忆力
也非常好,那么他很幸运,只需要花费一点时间即可重复实验。但是也有可能没那么幸
运,明明想重复实验却偏离了正确的前进道路,或是毫无意义地浪费一天去重复实验。
每当我给出这条建议时,总会有人说:“我不需要笔和纸就能做到!我可以写一段 Perl 脚
本去修改代码版本管理工具的命令,让它帮忙将每次运行的测试结果和所修改的代码一起
保存起来。如果我将测试结果保存在文件中......如果我在不同的目录下做测试......”
我并不想妨碍开发人员创新。如果你是一位主动吸收最佳实践的高级开发经理,那么尽管
这么做吧。不过我想说的是,使用纸和笔记录是一种很稳健、容易使用而且有着千年历史
的技术。即使在开发团队替换了版本管理工具或测试套件的情况下,这项技术仍然可用。
它还适用于开发人员的下一份工作。这项传统的解决方案仍然可以节省开发人员的时间。
测量基准性能并设定目标
独立开发人员可以随意地、迭代地进行优化,直到他觉得性能足够好了为止。不过工作于
团队中的开发人员需要满足经理和其他利益相关人员的需求。优化工作受两个数字主导:
优化前的性能基准测量值和性能目标值。测量性能基准不仅对于衡量每次独立的改善是否
成功非常重要,而且对于向其他利益相关人员就优化成本开销做出解释也是非常重要的。
而优化目标值之所以重要,是因为在优化过程中优化效果会逐渐变小。在优化过程的最初
阶段,树上总是有些容易摘取的挂得很低的水果:一些独立的进程或是想当然地编写的函
数,优化它们后可以使性能提升很多。但是一旦实现了这些简单的优化目标后,下一轮性
能提升就需要付出更多的努力。
做。幸运的是,差劲的性能往往表现得非常明显(例如用户界面长时间不响应、托管服
务器的规模没有可扩展性、按照 CPU 时间付费的成本非常高等)。一旦团队研究下性能问
题,那么目标数字很容易被设定下来。用户体验(UX)设计的一个学科分支专门研究用
户如何看待等待时间。下面是一份常用的性能测试项目清单,你可以从为这些项目设定性
从用户按下回车键直至程序进入主输入处理循环所经过的时间。通常,开发人员可以通
过测量程序进入 main() 函数到进入主循环的时间来得到启动时间,但是有时候也有例
外。为程序提供认证的操作系统厂商对程序在计算机启动时或某个用户登入时就运行有
严格的要求。例如,对那些寻求认证的硬件厂商,微软会要求 Windows shell 必须在启
动后 10 秒内能够进入它们的主循环。这限制了在忙碌的启动环境中,厂商可以预载和
启动的其他程序的数量。为此,微软提供了专用工具来帮助硬件厂商测量启动时间。
退出时间
从用户点击关闭图标或是输入退出命令直至程序实际完全退出所经过的时间。通常,开
发人员可以通过测量主窗口接收到关闭命令到程序退出 main() 的时间来得到退出时间,
但是有时候也有例外。退出时间也包含停止所有的线程和所依赖的进程所需的时间。为
程序提供认证的操作系统厂商对程序的退出时间有严格的要求。退出时间同样非常重
要,因为重启一个服务或是长时间运行的程序所需的时间等于它的退出时间加上它的启
动时间。
执行一个命令的平均时间或最长时间。对于网站来说,平均响应时间和最长响应时间
都会影响用户对网站的满意度。响应时间可以粗略地以 10 的幂为单位划分为以下几
个级别。
低于 0.1 秒:用户在直接控制
如果响应时间低于 0.1 秒,用户会感觉他们在直接控制用户界面,他们的操作直接
改变了用户界面。这是用户开始拖动对象至对象发生移动,或是用户点击输入框至
输入框变为高亮之间的最小延迟。任何高于这个值的延迟都会让用户觉得他们发送
了一条命令让计算机去执行。
0.1 秒至 1 秒:用户在控制命令
如果响应时间在 0.1 秒至 1 秒之间,用户虽然仍然会觉得他们处于掌控状态,但是
这个短暂的延迟会被用户理解为计算机执行了一条命令导致 UI 发生了变化。用户可
以忍受这种程度的延迟,不至于分散注意力。
1 秒至 10 秒:计算机在控制
如果响应时间在 1 秒至 10 秒之间,用户会觉得他们在执行了一条命令后失去了对
计算机的控制,虽然这时候计算机仍然在处理命令。用户可能会分散注意力,忘记
一件刚才发生的事情——他们需要完成自己的任务。10 秒是用户能保持注意力的
最长时间。如果他们多次遇到这种长时间等待 UI 发生改变的情况,用户满意度会
急速下降。
如果响应时间高于 10 秒,用户会觉得他们有足够的时间去做一些其他的事情。如
果他们的工作需要用到 UI,那么他们会利用等待计算机进行计算的时间去喝一杯
咖啡。如果可以的话,他们甚至会关闭程序,然后去其他地方找找满足感。
文章(https://www.nngroup.com/articles/powers-of-10-time-scales-in-ux/),这是一份出于
好奇而进行的学术研究。
吞吐量
与响应时间相对。通常,吞吐量表述为在一定的测试负载下,系统在每个时间单位内所
执行的操作的平均数。吞吐量所测量的东西与响应时间相同,但是它更适合于评估批处
理程序,如数据库和 Web 服务等。通常,这个数字越大越好。
有时,也可能会发生过度优化的情况。例如,在许多情况下,用户认为响应时间小于 0.1
秒就是一瞬间的事了。在这种情况下,即使将响应时间从 0.1 秒改善为了 1 毫秒,也不会
增加任何价值,尽管响应速度提升了 100 倍。
时的设置在许多方面都与处理客户数据的正式产品不同,在所有环境中都取得在测试过程
中测量到的性能改善结果是几乎不可能的。尽管某个任务在程序中负责大部分的逻辑处
理,但是使其变得更快可能仍然无法使整个程序变得更快。
例如,一个数据库开发人员通过执行 1000 次某个特定的查询语句分析了数据库性能,然
后基于分析结果进行了优化,但这可能并不会提升整个数据库的速度,而是只提升了该查
询语句的速度。这也可能会提升其他查询语句的速度,但它可能不会改善删除或更新查
询、建立索引或是数据库可以进行的其他处理的速度。