基准测试和性能分析
在本章中,我们将学习基准测试和性能分析如何帮助解决性能问题。
假设我们编写了一个代码,它也给出了预期的结果,但如果我们想让这个代码运行得更快一些,因为需求发生了变化。在这种情况下,我们需要找出代码的哪些部分正在减慢整个程序的速度。在这种情况下,基准测试和性能分析可能会有用。
什么是基准测试?
基准测试旨在通过与标准进行比较来评估某事。但是,这里出现的问题是,基准测试是什么,以及在软件编程的情况下为什么我们需要它。代码基准测试意味着代码执行的速度以及瓶颈在哪里。基准测试的一个主要原因是它优化了代码。
基准测试是如何工作的?
如果我们谈论基准测试的工作原理,我们需要从将整个程序作为当前状态进行基准测试开始,然后我们可以结合微基准测试,然后将程序分解成更小的程序。为了找到程序中的瓶颈并对其进行优化。换句话说,我们可以将其理解为将大型难题分解成一系列更小、更简单的难题,以便对其进行优化。
Python 基准测试模块
在 Python 中,我们有一个默认的基准测试模块,称为timeit。借助timeit模块,我们可以测量主程序中一小段 Python 代码的性能。
示例
在以下 Python 脚本中,我们导入了timeit模块,该模块进一步测量执行两个函数——functionA 和functionB——所需的时间。
import timeit import time def functionA(): print("Function A starts the execution:") print("Function A completes the execution:") def functionB(): print("Function B starts the execution") print("Function B completes the execution") start_time = timeit.default_timer() functionA() print(timeit.default_timer() - start_time) start_time = timeit.default_timer() functionB() print(timeit.default_timer() - start_time)
运行上述脚本后,我们将获得如下所示的两个函数的执行时间。
输出
Function A starts the execution: Function A completes the execution: 0.0014599495514175942 Function B starts the execution Function B completes the execution 0.0017024724827479076
使用装饰器函数编写我们自己的计时器
在 Python 中,我们可以创建我们自己的计时器,它就像timeit模块一样工作。这可以通过装饰器函数来完成。以下是一个自定义计时器的示例:
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} took {time} seconds to complete its execution." 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 脚本有助于导入随机时间模块。我们创建了 timer_func() 装饰器函数。它内部包含 function_timer() 函数。现在,嵌套函数将在调用传入的函数之前获取时间。然后它等待函数返回并获取结束时间。这样,我们最终可以让 Python 脚本打印执行时间。该脚本将生成如下所示的输出。
输出
Myfunction took 8.000457763671875 seconds to complete its execution.
什么是性能分析?
有时程序员希望测量程序的一些属性,例如内存使用情况、时间复杂度或特定指令的使用情况,以测量该程序的真实能力。对程序进行此类测量称为性能分析。性能分析使用动态程序分析来进行此类测量。
在后续部分,我们将学习有关不同 Python 性能分析模块的信息。
cProfile – 内置模块
cProfile 是 Python 的一个内置性能分析模块。该模块是一个 C 扩展,开销合理,使其适合于分析长时间运行的程序。运行后,它会记录所有函数和执行时间。它非常强大,但有时有点难以解释和操作。在下面的示例中,我们在以下代码上使用 cProfile:
示例
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("x = {1} after Iteration {0}".format(i,x))
以上代码保存在thread_increment.py文件中。现在,在命令行上使用 cProfile 执行代码,如下所示:
(base) D:\ProgramData>python -m cProfile thread_increment.py x = 100000 after Iteration 0 x = 100000 after Iteration 1 x = 100000 after Iteration 2 x = 100000 after Iteration 3 x = 100000 after Iteration 4 3577 function calls (3522 primitive calls) in 1.688 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 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 - 它指的是 tottime 除以 ncalls 的商。
cumtime - 它表示在此函数及其所有子函数中花费的累积时间。对于递归函数来说,它甚至更准确。
percall - 它指的是 cumtime 除以原始调用的商。
filename:lineno(function) - 它基本上提供了每个函数的相应数据。