要想深入地理解性能分析,很重要的一点是明白性能分析方法究竟能够分析什么指标。因为测量是性能分析的核心,所以让我们仔细看看程序运行时可以测量的指标。 1.3.1 运行时间 做性能分析时,我们能够收集到的最基本的数值就是运行时间。整个进程或代码中某个片段的运行时间会暴露相应的性能。如果你对运行的程序有一些经验(比如说你是一个网络开发者,正在使用一个网络框架),可能很清楚运行时间是不是太长。例如,一个简单的网络服务器查询数据库、响应结果、反馈到客户端,一共需要100毫秒。但是,如果程序运行得很慢,做同样的事情需要花费60秒,你就得考虑做性能分析了。你还需要考虑不同场景的可比性。再考虑另一个进程:一个MapReduce任务把2TB数据存储到文件中要消耗20分钟,这时你可能不会认为进程很慢了,即使它比之前的网络服务器处理时间要长很多。 为了获得运行时间,你不需要拥有大量性能分析经验和一堆复杂的分析工具。你只需要把几行代码加入程序运行就可以了。 例如,下面的代码会计算斐波那契数列的前30位: import datetime tstart = None tend = None def start_time(): global tstart tstart = datetime.datetime.now() def get_delta(): global tstart tend = datetime.datetime.now() return tend - tstart def fib(n): return n if n == 0 or n == 1 else fib(n-1) + fib(n-2) def fib_seq(n): seq = [] if n > 0: seq.extend(fib_seq(n-1)) seq.append(fib(n)) return seq start_time() print "About to calculate the fibonacci sequence for the number 30" delta1 = get_delta() start_time() seq = fib_seq(30) delta2 = get_delta() print "Now we print the numbers: " start_time() for n in seq: print n delta3 = get_delta() print "====== Profiling results =======" print "Time required to print a simple message: %(delta1)s" % locals() print "Time required to calculate fibonacci: %(delta2)s" % locals() print "Time required to iterate and print the numbers: %(delta3)s" % locals() print "====== =======" 程序的输出结果如下所示: About to calculate the Fibonacci sequence for the number 30 Now we print the numbers: 0 1 1 2 3 5 8 13 21 #……省略一些数字 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 ====== Profiling results ======= Time required to print a simple message: 0:00:00.000030 Time required to calculate fibonacci: 0:00:00.642092 Time required to iterate and print the numbers: 0:00:00.000102 通过最后三行结果,我们会发现,代码中最费时的部分就是斐波那契数列的计算。 下载源代码 你可以用自己的账户登录http://www.packtpub.com,下载你购买过的Packt出版社的所有图书的示例代码。如果你是在其他地方购买的Packt出版社的书籍,可以通过http://www.packtpub.com/support注册账户,然后要求Packt把示例代码通过邮件发给你。 1.3.2 瓶颈在哪里 只要你测量出了程序的运行时间,就可以把注意力移到运行慢的环节上做性能分析。通常,瓶颈都是由下面的一种或几种原因造成的。 沉重的I/O操作,比如读取和分析大文件,长时间执行数据库查询,调用外部服务(比如HTTP请求),等等。 出现了内存泄漏,消耗了所有的内存,导致后面的程序没有内存来正常执行。 未经优化的代码被频繁地执行。 密集的操作在可以缓存时没有缓存,占用了大量资源。 I/O关联的代码(文件读/写、数据库查询等)很难优化,因为优化有可能会改变程序执行I/O操作的方式(通常是语言的核心函数操作I/O)。相反,优化计算关联的代码(比如程序使用的算法很糟糕),改善性能会比较容易(并不一定很简单)。这是因为优化计算关联的代码就是改写程序。 在性能优化接近尾声的时候,剩下的大多数性能瓶颈都是由I/O关联的代码造成的。