基准测试和分析(Benchmarking & Profiling)

优质
小牛编辑
130浏览
2023-12-01

在本章中,我们将学习基准测试和分析如何帮助解决性能问题。

假设我们编写了一个代码并且它也提供了所需的结果,但是如果我们想要更快地运行此代码,因为需求已经改变了。 在这种情况下,我们需要找出代码的哪些部分正在减慢整个程序的速度。 在这种情况下,基准测试和分析可能很有用。

什么是基准测试?

基准测试旨在通过​​与标准进行比较来评估某些内容。 然而,这里出现的问题是什么是基准测试以及为什么在软件编程的情况下我们需要它。 对代码进行基准测试意味着代码的执行速度和瓶颈所在。 基准测试的一个主要原因是它优化了代码。

基准测试如何运作?

如果我们谈论基准测试的工作,我们需要首先将整个程序作为一个当前状态进行基准测试然后我们可以结合微基准测试然后将程序分解为更小的程序。 为了找到我们计划中的瓶颈并进行优化。 换句话说,我们可以将它理解为将大而难的问题分解为一系列更小且更容易优化它们的问题。

Python module for benchmarking

在Python中,我们有一个默认的基准测试模块,称为timeit 。 在timeit模块的帮助下,我们可以在主程序中测量一小部分Python代码的性能。

例子 (Example)

在下面的Python脚本中,我们将导入timeit模块,该模块进一步测量执行两个函数所需的时间 - functionAfunctionB -

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)

运行上面的脚本后,我们将得到两个函数的执行时间,如下所示。

输出 (Output)

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模块一样。 它可以在decorator功能的帮助下完成。 以下是自定义计时器的示例 -

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脚本打印执行时间了。 该脚本将生成输出,如下所示。

输出 (Output)

Myfunction took 8.000457763671875 seconds to complete its execution.

什么是剖析?

有时程序员想要测量一些属性,如使用内存,时间复杂度或使用有关程序的特定指令来测量程序的实际能力。 这种关于程序的测量称为剖析。 分析使用动态程序分析来进行此类测量。

在随后的部分中,我们将了解用于分析的不同Python模块。

cProfile - 内置模块

cProfile是一个用于分析的Python内置模块。 该模块是一个C扩展,具有合理的开销,使其适用于分析长时间运行的程序。 运行后,它会记录所有功能和执行时间。 它非常强大,但有时难以解释和采取行动。 在以下示例中,我们在下面的代码中使用cProfile -

例子 (Example)

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) - 它基本上提供了每个函数的相应数据。