2.3 日志输出模块的性能提升与改进方式
日志输出模块作为Python中直接和用户进行交互的模块,在不同的Python版本中都发挥着至关重要的作用。我们在Python程序开发过程中,或多或少都需要输出程序的执行结果,或者在程序的执行过程中输出不同步骤的结果,以调试程序。不仅在传统的Python程序开发中,我们需要将程序的执行结果以日志形式输出,在Python Web框架开发、机器学习和人工智能领域,都需要将程序的最终执行结果或阶段性结果以日志的形式输出,从而让开发者了解Python服务是否正常运行,及判断执行结果是否符合预期。
CPython官方在实现Python时,将日志输出模块的实现作为核心任务之一,并且在Python 2.7.18和Python 3.9.13中对日志输出模块进行了升级和改良。
2.3.1 传统的日志输出方式与性能分析
在Python中,传统的日志输出其实就是将程序的执行结果进行打印,以便让用户了解自己的程序设计。无论Python 2.7.18版本还是Python 3.9.13版本,我们都知道打印一行日志的最简单的方式就是用print语句,如下所示。
执行上述代码之后,代码命令行中输出“Hello Python”字样。那么,如果使用内置日志输出print函数,结果如何呢?
对于程序性能的分析,无非从两个角度考虑:程序执行的时间和程序执行所需要占用的空间,也就是所需要的内存大小。对于日志输出程序的性能分析,我们完全可以采用Python编码实现,且这个分析过程比较简单,代码如下所示。
为了获得更加准确的性能测试结果,我们会调用上述代码10次,并记录不同调用次数所对应的程序执行时间和内存占用情况,如表2-1所示。
表2-1 传统日志打印程序的执行时间和内存占用情况统计
通过表2-1可以得出,随着执行次数的增加,上述程序内存占用并不会发生改变,这是由于我们自始至终都是打印相同的数据,数据本身并没有发生变化,所以数据所占内存的大小也就不会发生变化。而随着打印次数的增加,执行完成上述代码所需要消耗的时间在动态变化,这也足够说明计算机在处理相同代码片段时,由于不同的CPU执行时机不同,耗时不相等。
我们可以使用sys库中的getsizeof函数查看Python内置print函数本身所占的内存大小,这里我们直接给出数据(16B)。该数值大小是由CPython的实现决定的,并不会因操作系统不同而改变。唯一可以影响上述程序所占内存的因素就是我们使用print函数来打印的数据本身的大小,所需打印的数据本身越大,执行程序所需要的内存空间也就越大,所需要消耗的时间也就越长;所需打印的数据本身越小,执行程序所需要的内存空间也就越小,所需要消耗的时间也就越短。
以上述程序片段为例,使用print函数来打印“Hello Python”最短用时23μs,最长用时32μs,这表明当我们在使用print函数打印一般日志内容时,所消耗的最短时间和最长时间都是在微秒范围内(特别长的内容除外),并不会上升到秒计时单位,所以我们在日常使用Python进行程序设计和开发时,可以放心地使用print函数,不需要担心由于使用print函数而带来额外的性能开销。
2.3.2 基于Logging模块的日志输出方式
Logging模块和之前介绍的print函数均是Python内置的日志输出方式,只不过Print函数更为人们所熟知。Logging模块是在Print函数基础之上封装实现的,且CPython官方将其解释为根据经典的Log4J日志框架实现的,即Logging模块是基于Log4J日志框架来实现的。Log4J日志框架在Python中使用较少,但是在Java中广为流传和使用,特别是在Java Web领域,已经有非常多的人在使用。而在Python Web领域,人们更多是使用Python内置的Logging模块来作为系统或项目主要的日志输出框架。
就像分析print函数性能一样,我们也来分析一下Logging模块的执行时间和所占内存,代码如下所示。
同样,我们调用上述代码10次,并记录不同调用次数所对应的程序执行时间和内存占用情况,如表2-2所示。
表2-2 基于Logging模块的日志打印方式执行时间和内存占用情况统计
通过表2-2可以得出,Logging模块的执行时间和内存占用规律与print函数整体类似。随着执行次数的增加,Logging模块打印一条Debug级别的日志,所占内存为91B。由于打印的日志内容没有发生变化,要打印的日志内容所占内存大小也没有发生变化,也就是无论重复打印多少次,只要我们所打印的日志内容没有发生变化,那么使用Logging模块打印一条Debug级别的日志所占的内存空间就不会发生改变。
随着执行次数的增加,由于不同CPU具体执行代码的时机不同,所以每次执行代码的耗时会不一样,这里和print函数的执行性能规律类似。可以看出,使用Logging模块来打印一条Debug级别的日志,最短需要32μs,最长需要56μs,且大概率会花费32~35μs。这是由于CPython对Logging模块持续进行优化,打印日志耗时趋于平稳,时间波动不大。
2.3.3 两种方式的对比
通过上述对print函数和Logging模块的执行耗时和内存占用的分析和比较可以得出,如果使用Python做传统开发,我们就可以直接使用print函数来打印需要输出的内容;如果使用Python做Web开发,我们就可以直接使用Logging模块来打印需要输出的内容,不建议使用print函数,因为在Web环境中,需要考虑日志打印的稳定性和性能开销。print函数虽说开销不大,但是它并不稳定,并没有像Logging模块那样得到CPython官方的持续优化。这也是print函数和Logging模块的最大区别。