第6章
性能 过早的优化是程序
设计中的万恶之源。 -Donald Knuth (attributed to C. A. R. Hoare)
性能是只有趣的怪兽。性能优化名声并不太好,因为它通常执行得太早,太频繁,以至于会牺牲可读性,可维护性, 甚至正确性。Rails 已经足够快了,但如果不小心的话还是可能让它变慢. 在进行性能优化时请记住以下几点: 算法改进优于代码优化 尝试压榨每个代码片段最后一个 bit 的性能很有诱惑性,但通常会让你迷失大局。无论怎么优化 C 或汇编代码都不 能使冒泡排序比快速排序更快。自上而下开始优化。 作为一个通用规则,可维护性优于性能 代码首先需要容易被读懂,然后才是速度优化。 只优化有问题的部分 通常情况下,代码的时间分布不均匀: 80%的时间花在 20%的代码上。把有限的资源花在能带来重要性能提升的 地方才有价值。 三思而后行 度量是搞清楚你的代码把时间花在哪里的唯一方法。就像做木工活一样,如果不知道哪些地方需要改进就动手修 改的话会浪费大量时间。在这一章里,我们将一起来探索一些能帮助我们找出问题根源的最好的工具和方法。 6.1 度量工具 当然,为了正确测量性能,我们需要一些工具。这部分是关于 Ruby 和 Rails 代码的分析,也包括一般的以用 web 应用分析的。有一系列工具可用来分析整个 Rails 堆栈,从 HTTP 一直到 Ruby 内部。 6.1.1 黑盒分析 你会感兴趣的最基本的高级度量是:在典型情况下,服务器处理请求有多快?即使回答有些模糊,并与典型情况下的 实际性能关系不大时,它仍然对测试缓存或部署一组新特性时的回归测试有用。 这项技术被称作黑盒分析:把服务器当作一个黑盒子来测量它可以处理多大的流量。现在不关心盒子里边是什么,只 关心它处理请求有多快。我们先避开分析和调优的细节。 在这个阶段,我们需要一个基准测试工具──但是首先,先简短地转移到数学世界看看。 6.1.1.1 统计分析:必须知道的东西 统计分析: 正确理解黑盒分析的结果不需要太多的统计分析知识,但有些东西还是必须要知道的。 统计分析处理多个样本结果,在这个例子里指 HTTP 响应次数。依据 Ruby 风格,我们以一个数组为例来说明: samples = %w(10 11 12 10 10).map{|x|x.to_f} 样本的平均值为样本之和除以样本数量。以 Ruby 方式直接表达是向 Enumerable 里添加一些方法: module Enumerable def sum(identity = 0) inject(identity) {|sum, x| sum + x} end def mean sum.to_f / length end end 它给我们一个预期的结果: samples.sum # => 53.0 samples.length # => 5 samples.mean # => 10.6 所有人都熟悉平均值,但平均值自身对描述数据集几乎没有价值。看看下面这两组样本: samples1 =
%w(10 11 12 10 10 9 12 10 9 9).map{|x|x.to_f} samples2 = %w( 2 11 6 14 20 21 3 4 8 13).map{|x|x.to_f} 这两个数据集有相同的平均值,10.2。但从图中(图 6-1)可以看出,它们明显是两种不同的性能分析结果。 Figure 6-1 两个平均值相同,但表现非常不同的响应时间分析结果 我们需要一种新的统计分析方法来测量数据与平均值的差异有多大。这种统计方法是标准方差。样本的标准方差是指 样本中每项与平均值差的平方和除以样本数量再开方。在 Ruby 中,可以如下实现: module Enumerable def population_stdev
Math.sqrt( map{|x| (x - mean) ** 2}.mean ) end end 这段代码对集合作映射,计算每项与平均值之差的平方。然后计算这些平方结果的平均值,再开方,结果即为标准方 差。 不过,这还只解决了一半
问题。目前介绍的只是总体标准方差,而我们最终想要的是样本的标准方差。这两者的根本 区别在于数据表现的是整体还是部分。 对 于我们应用程序响应时间的数据集,如果想要推断出一个平均值和一个可以应用到没采样的数据点的置信区间, 那么我们需要使用样本标准方差。使用总体