通过上述对Profile的介绍,我们知道了Profile在大多数情况下被称为Python中的代码分析工具,可统计分析给定Python方法在CPython解释器或虚拟机中占用的内存,以及其他基本参数。那么,到底如何使用Profile来对Python代码进行分析呢?下面笔者会通过一个分析示例来总结使用Profile分析Python代码的步骤。
为了方便演示Profile对Python代码的分析结果,笔者这里定义了一个简单的Python方法,代码如下:
上述代码非常简单,笔者在这里就不再介绍了。使用Profile来对上述helloProfile()方法进行分析的过程如下:
在上述代码中,首先将Profile的一种实现cProfile通过import导入包引入,然后调用cProfile中的run()方法来对helloProfile()方法进行代码分析。cProfile中的run()方法支持传递Python中的方法名称、Python中对应方法的参数,还支持将Python方法的分析结果写入对应的文件。执行上述代码便开启对helloProfile()方法的分析,并直接将分析结果进行打印,如图14-2所示。
上述代码中,第一行“hello profile”是helloProfile()方法的输出内容,和Profile分析内容无关。第二行“2 function calls in 0.000 seconds”是使用Profile的概括性分析内容,表示Profile监听到2个方法调用,并且这一监听过程在0.000秒内完成,这意味着调用不是通过递归引发的。第三行“Ordered by: standard name”表示监听结果内容的输出顺序是按照最右边列中的文本字符串的顺序依次进行输出。第四行开始直到最后一行是Profile对helloProfile()方法的详细分析内容,共包含6列,每一列所代表的的含义如下。
● ncalls列表示该Python方法被调用的次数。如果该列中有两个数字同时出现,例如3/1,那么这两个数字就表示该Python方法发生了递归调用,具体为这两个数字中的第二个数字是Python方法的原始调用次数,第一个数字是调用的总次数。注意,当Python方法不发生递归调用时,这两个数字总是相同的,并且只打印这两个数字中的一个数字。
● tottime列表示指定的Python方法执行消耗的总时间,不包括调用子方法的时间,单位为s。
● percall列表示tottime列除以ncalls列的值。
● cumtime列表示指定的Python方法及其所有子方法(从调用到退出)消耗的累积时间。这个数字对于递归函数来说是准确的,单位为s。
● percall列表示cumtime列除以原始调用次数的值,即函数运行一次的平均时间,单位为s。
● filename:lineno(function)列表示提供相应数据的每个Python方法。
如果需要将测试结果输出到文件中,那么我们可以像下面这样编写代码:
执行上述代码之后,demo.txt文件就会在当前Python文件所在目录中生成,如图14-3所示。
双击打开该demo.txt文件即可查看性能检测数据。这种将性能测试报告输出到文件中查看的形式非常实用,例如,在多部门对代码进行评审或研讨时,可以直接将该性能测试报告进行投屏,还是很方便的。
在理解了上述分析内容中每一列所代表的含义后,让我们来看一下Profile中的run()方法的定义:
profile中的run()方法接收3个参数,第一个参数command表示传入的Python方法的名称,也可以传入固定的Python代码,这些都是允许的。第二个参数是fileName,即文件名称,如果传递了fileName,那么第一个参数的分析结果就会直接写入该fileName中,开发者需要通过对应的fileName来查看分析结果,且不会在控制台将分析结果进行打印。第三个参数是sort,表示排序方式,默认值为-1,表示采用默认方式进行排序,即采用上述示例代码中的Ordered by:standard name方式对代码的分析结果列进行输出排序。其实,调用上述run()方法之后,本质上是执行的下述exec()方法。exec()方法的官方定义如下:
可以看出,exec()方法的官方定义与run()方法的官方定义几乎相同,这里不再赘述。
通过对run()方法打印结果的输出内容进行统计与分析,我们可以判断出开发者所开发的Python程序是否正常运行,比如一个递归方法应该执行多少次递归调用,通过ncalls列即可查看,再比如,开发者在处理业务代码时,如果该业务代码对执行的时间有严格要求,那么就可以通过Tottime列查看该代码所执行的真正时间,从而可以判断出开发者开发的代码有没有按照预期的执行时间执行,如果执行时间超过这一范围,就需要开发者对代码进行一定的优化,之后再次调用run()方法来查看代码的执行时间。
诸如此类的代码测试还有很多,开发者需要根据实际业务场景和性能指标,结合Profile的run()方法进行监控,以此达到优化Python程序的目的。
综上所述,使用Profile对Python代码的分析步骤总结如下。
第一步,定义进行分析的Python方法或代码行,可以是已经开发好的Python方法,也可以是开发好的Python代码,只要有Python方法或代码行即可。理论上来说,该Python方法或代码可以无限大。
第二步,根据当下的Python运行环境,决定是采用cProfile还是Profile实现第一步中定义的Python方法或代码的性能分析。选用哪种实现的方法很简单,我们可以直接通过import来对cProfile引入,引入完成再调用该代码时,如果控制台报错,即没有cProfile模块或找不到cProfile模块,说明当下的Python环境不支持cProfile,此时就只能使用Profile进行性能优化。
如果在引入Profile之后,控制台还是报错Profile模块找不到或不存在,说明当下的Python环境不支持Profile,需要升级到较新版本或最新的Python版本。
第三步,根据第二步选择的Profile,决定调用性能分析的Profile方法,最后根据实际的业务需要结合性能分析结果,对开发者开发的Python方法或代码进行深入分析,并结合分析结果对开发者所开发的Python方法或代码进行整改和优化,以满足业务需要。