在本章中,我们将学习基准测试和分析如何帮助解决性能问题。
假设我们已经编写了一个代码,并且它也给出了期望的结果,但是如果想要更快地运行此代码,因为需求已经发生了变化。 在这种情况下,需要找出代码的哪些部分正在减慢整个程序。 在这种情况下,基准测试和分析可能很有用。
基准测试是什么?
基准测试旨在通过与标准进行比较来评估某些事物。 然而,这里出现的问题是,什么是基准,以及为什么需要软件编程。 对代码进行基准测试意味着代码的执行速度以及瓶颈的位置。 基准测试的一个主要原因是它优化了代码。
基准是如何工作?
如果我们谈论基准测试的工作,需要首先将整个程序作为一个当前状态,然后可以将微基准结合起来,然后将程序分解成更小的程序。 找到程序中的瓶颈并优化它。 换句话说,我们可以把它理解为将大而难的问题分解为一系列较小和较容易的问题来优化它们。
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):
"""
A timer decorator
"""
def function_timer(*args, **kwargs):
"""
A nested function for timing other functions
"""
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)
- 它基本上提供了每个函数的相应数据。