摘要:本文介绍了基准测试和性能分析在优化程序性能中的应用。基准测试通过对比标准评估代码执行速度,Python的timeit模块可用于测量代码耗时,也可通过装饰器自定义计时器。性能分析则测量内存、时间复杂度等属性,cProfile模块能记录所有函数调用及耗时情况,输出包含调用次数、总耗时等指标,帮助定位性能瓶颈。文章通过具体代码示例展示了timeit和cProfile的使用方法,为程序性能优化提供了实用工具和技术方案。
目录
[Python 基准测试模块](#Python 基准测试模块)
基准测试与性能分析
在本章中,我们将学习基准测试和性能分析如何帮助解决程序性能问题。
假设我们编写的代码能输出预期结果,但随着需求变化,希望让代码运行得更快。这时我们需要找出拖慢整个程序运行速度的代码段,而基准测试和性能分析就能发挥作用。
什么是基准测试?
基准测试的核心是通过与标准对比来评估事物的性能。而在软件编程中,我们需要明确:编程中的基准测试指什么,又为何需要它?代码的基准测试,是检测代码的执行速度,定位性能瓶颈所在。开展基准测试的一个重要目的,就是对代码进行性能优化。
基准测试的工作原理
谈及基准测试的执行逻辑,首先需要对整个程序的当前运行状态做一次整体基准测试,随后结合微基准测试,将整个程序拆解为多个小程序模块。通过这种方式找出程序内部的性能瓶颈,进而开展优化工作。换句话说,这一过程就是把复杂的大问题拆解为一系列相对简单的小问题,再逐一进行优化。
Python 基准测试模块
Python 内置了专门用于基准测试的模块 ------timeit 。借助timeit模块,我们可以在主程序中测量小段 Python 代码的执行性能。
示例
在以下 Python 脚本中,我们导入timeit 模块,用于分别测量functionA 和functionB两个函数的执行耗时:
python
import timeit
import time
def functionA():
print("函数A开始执行:")
print("函数A执行完成:")
def functionB():
print("函数B开始执行")
print("函数B执行完成")
start_time = timeit.default_timer()
functionA()
print(timeit.default_timer() - start_time)
start_time = timeit.default_timer()
functionB()
print(timeit.default_timer() - start_time)
运行上述脚本后,我们将得到两个函数的执行耗时,输出结果如下。
输出结果
plaintext
函数A开始执行:
函数A执行完成:
0.0014599495514175942
函数B开始执行
函数B执行完成
0.0017024724827479076
利用装饰器自定义计时器
在 Python 中,我们可以自定义一个计时器,实现和timeit 模块相同的功能,这一需求可以通过装饰器函数来完成。以下是自定义计时器的示例:
python
import random
import time
def timer_func(func):
def function_timer(*args, **kwargs):
start = time.time()
value = func(*args, **kwargs)
end = time.time()
runtime = end - start
msg = "函数{func}的执行耗时为{time}秒。"
print(msg.format(func = func.__name__,time = runtime))
return value
return function_timer
@timer_func
def Myfunction():
for x in range(5):
sleep_time = random.choice(range(1,3))
time.sleep(sleep_time)
if __name__ == '__main__':
Myfunction()
上述 Python 脚本导入了 random 和 time 模块,我们定义了装饰器函数 timer_func (),该函数内部嵌套了 function_timer () 函数。这个嵌套函数会在调用传入的目标函数前记录开始时间,等待目标函数执行完毕后记录结束时间,最终让脚本打印出目标函数的执行耗时。脚本的输出结果如下。
输出结果
plaintext
函数Myfunction的执行耗时为8.000457763671875秒。
什么是性能分析?
有时程序员需要测量程序的各项属性,比如内存占用、时间复杂度,或是特定指令的使用情况,以此评估程序的实际性能。这类对程序各项属性的测量行为,就是性能分析。性能分析通过动态程序分析技术来完成这些测量工作。
在后续的小节中,我们将学习用于性能分析的各类 Python 模块。
内置性能分析模块:cProfile
cProfile是 Python 的内置性能分析模块,该模块基于 C 语言扩展开发,运行开销合理,适合对长时间运行的程序做性能分析。运行该模块后,它会记录下程序中所有函数的调用情况及执行耗时。cProfile 功能强大,但有时其输出结果的解读和实际应用会有一定难度。以下是使用 cProfile 对一段代码做性能分析的示例:
示例
python
def increment_global():
global x
x += 1
def taskofThread(lock):
for _ in range(50000):
lock.acquire()
increment_global()
lock.release()
def main():
global x
x = 0
lock = threading.Lock()
t1 = threading.Thread(target=taskofThread, args=(lock,))
t2 = threading.Thread(target= taskofThread, args=(lock,))
t1.start()
t2.start()
t1.join()
t2.join()
if __name__ == "__main__":
for i in range(5):
main()
print("第{0}次循环后,x = {1}".format(i,x))
将上述代码保存为thread_increment.py文件,随后在命令行中结合 cProfile 执行该文件,命令如下:
plaintext
(base) D:\ProgramData>python -m cProfile thread_increment.py
第0次循环后,x = 100000
第1次循环后,x = 100000
第2次循环后,x = 100000
第3次循环后,x = 100000
第4次循环后,x = 100000
3577次函数调用(其中3522次原生调用),总耗时1.688秒
按标准名称排序
调用次数 总耗时 单次耗时 累积耗时 单次累积耗时 文件名:行号(函数名)
5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:103(release)
5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:143(__init__)
5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:147(__enter__)
从上述输出结果可以看出,cProfile 打印出了程序中全部 3577 次的函数调用记录,包含每个函数的耗时以及调用次数。输出结果中的各列含义如下:
- ncalls(调用次数):函数被调用的总次数。
- tottime(总耗时):在指定函数中执行所花费的总时间(不包含调用子函数的耗时)。
- percall(单次耗时):总耗时除以调用次数的结果。
- cumtime(累积耗时):在指定函数及其所有子函数中执行所花费的总时间,该指标对递归函数同样精准。
- percall(单次累积耗时):累积耗时除以原生调用次数的结果。
- filename:lineno (function)(文件名:行号 (函数名)):对应每个函数的文件、行号及函数名信息。