python性能分析(一)——使用timeit给你的程序打个表吧

前言

我们可以通过查看程序核心算法的代码,得知核心算法的渐进上界或者下界,从而大概估计出程序在运行时的效率,但是这并不够直观,也不一定十分靠谱(在整体程序中仍有一些不可忽略的运行细节在估计时被忽略了),因此在实际评测程序时我们还是需要实际的考量程序的运行时间和瓶颈,最好具体到执行一段代码多少次,执行一段代码花了多少时间,幸好的是Python自带了许多有用的工具,可以帮助我们实现这些要求,下面是一些我在学习中记录的笔记,从简单到复杂介绍了python性能分析的方法,希望我的笔记能帮到您。

注:写作不易,转载请注明出处,谢谢支持~


目录


一、使用timeit计算程序耗时

timeit可以在命令行通过-m指令导入作为脚本运行,也可以在代码内import导入使用,它会将代码执行多遍,然后得出耗时最短的时间是多少,下面是具体的几种使用方式:
注:我在实际测试时使用的是python3.7的环境,在文末,有举例说明python2和python3.7使用时实际上不同的地方


1. 命令行执行

1.1 通过在命令行编译时添加-m指令,来进行timeit的导入,后面跟着一段字符串,包含用来测试的表达式。(结果中的usec为微秒)
在这里插入图片描述
您也可以直接在字符串中包含你要测试的模块名,python会直接执行它,并用timeit得出时间:
在这里插入图片描述
这里在命令行的工作目录下,有一个test_timeit.py文件,其中的内容如下:

def factorial(n):
    if n == 1: return 1
    return n * factorial(n-1)

if __name__ == "__main__":
    factorial(30)

timeit计算了执行main的时间


1.2 通过添加 -n N命令可以设置语句执行的次数:
在这里插入图片描述


1.3 通过添加-r N设置计时器重复多少次(默认是5次)(最后的结果是取平均?):
在这里插入图片描述


1.4 通过添加-s str 设置str语句只在初始化的时候执行一遍,后面会pass这个语句:
在这里插入图片描述
图片太小,把命令写一下:

python -m timeit -n 100 -r 5 -s "from test_timeit import factorial" "factorial(20)"

其中-s后跟着的"from test_timeit import factorial"只在第一次时执行了
其中的factorial是一个计算阶乘的小函数,代码如下:

def factorial(n):
   if n == 1: return 1
   return n * factorial(n-1)

1.5 通过添加-t 使用time.time() (default on Unix)

time.time()
返回从纪元(1970.1.1)至今的秒数。虽然这个函数的返回值永远为浮点数,
但并不是所有系统提供的秒数都会精确到小数点以后。
一般情况下这个函数的返回值不会小于它上一次被调用的返回值,除非系统时钟在两次调用之间发生了重置。
//参考https://www.cnblogs.com/cuixiaochen/p/4722387.html


1.6 通过添加-c使用 time.clock() (default on Windows)

time.clock()
在Unix 中,将当前的处理器时间以浮点数的形式返回,单位为秒。
它的精确度(准确地说是“处理器时间”的精确度)取决于同名的C函数,
无论如何,这个函数是python关于时间计算的标尺。
WINDOWS中,第一次调用,返回的是进程运行的实际时间。
而第二次之后的调用是自第一次调用以后到现在的运行时间。
(实际上是以WIN32上QueryPerformanceCounter()为基础,它比毫秒表示更为精确)
在windows中,time.clock()更准确。//参考https://www.cnblogs.com/cuixiaochen/p/4722387.html

在windows中使用-t-c: 可以发现二者是有区别的,这个选项完全可以遵循默认,timeit会自动使用更精确的时间计算方法
在这里插入图片描述


1.7 通过添加-v,打印原始计时结果,以获得更高的数字精度,并且显示更具体的结果
在这里插入图片描述


1.8 通过-u,设置计时单位:
可选项包括:nsec(纳秒),usec(微秒),msec(毫秒),sec(秒)
在这里插入图片描述


1.9 通过添加-p计算处理时间,而不是wallclock(从测试开始到结束所用的时间,以及 CPU 时间,即 CPU 上总的处理时间)
在这里插入图片描述


1.10 执行多行代码,只需在后面按顺序添加多个表达式即可:
在这里插入图片描述


1.11 通过添加-v打印帮助python -m timeit -h,打印的内容如下:

C:\python37\mypython\learning test 2019-03>python -m timeit -h
Tool for measuring execution time of small code snippets.

This module avoids a number of common traps for measuring execution
times.  See also Tim Peters' introduction to the Algorithms chapter in
the Python Cookbook, published by O'Reilly.

Library usage: see the Timer class.

Command line usage:
   python timeit.py [-n N] [-r N] [-s S] [-p] [-h] [--] [statement]

Options:
 -n/--number N: how many times to execute 'statement' (default: see below)
 -r/--repeat N: how many times to repeat the timer (default 5)
 -s/--setup S: statement to be executed once initially (default 'pass').
               Execution time of this setup statement is NOT timed.
 -p/--process: use time.process_time() (default is time.perf_counter())
 -v/--verbose: print raw timing results; repeat for more digits precision
 -u/--unit: set the output time unit (nsec, usec, msec, or sec)
 -h/--help: print this usage message and exit
 --: separate options from statement, use when statement starts with -
 statement: statement to be timed (default 'pass')

A multi-line statement may be given by specifying each line as a
separate argument; indented lines are possible by enclosing an
argument in quotes and using leading spaces.  Multiple -s options are
treated similarly.

If -n is not given, a suitable number of loops is calculated by trying
successive powers of 10 until the total time is at least 0.2 seconds.

Note: there is a certain baseline overhead associated with executing a
pass statement.  It differs between versions.  The code here doesn't try
to hide it, but you should be aware of it.  The baseline overhead can be
measured by invoking the program without arguments.

Classes:

   Timer

Functions:

   timeit(string, string) -> float
   repeat(string, string) -> list
   default_timer() -> float

返回目录


2. 导入使用

2.1 使用timeit.timeit
timeit.timeit(stmt='pass', setup='pass', timer=, number=1000000, globals=None)
参数解释:

  • stmt 语句,要执行的表达式,多个语句可以使用;分开
  • setup 语句,只在第一次初始化时执行的表达式,在之后会跳过
  • timer 计时器,默认是time.perf_counter()
  • number 执行次数
  • globals 全局变量,需要是个字典

使用实例:
在这里插入图片描述
Q&A: 为什么不带number参数会比number=10**5时所费时间更长呢?

  • 因为number的默认值是10**6

Q&A: 输出结果的默认单位是什么?

  • usec,微秒

通过setup参数,设置初始执行的语句,下例是’x = 2’,当重复执行多遍时,'x = 2’只在第一次执行一次。
在这里插入图片描述
通过globals携带全局变量
在这里插入图片描述

执行多个语句
在这里插入图片描述


2.2 使用timeit.repeat,输出的是个列表,包含重复次数个的测试时间
timeit.repeat(stmt='pass', setup='pass', timer=, repeat=3, number=1000000, globals=None)
参数解释:

  • stmt 语句,要执行的表达式,多个语句可以使用;分开
  • setup 语句,只在第一次初始化时执行的表达式,在之后会跳过
  • timer 计时器,默认是time.perf_counter()
  • repeat 重复次数,默认是5
  • number 执行次数
  • globals 全局变量,需要是个字典

使用起来与timeit.timeit类似,只是多了repeat参数,下例将timeit.repeat和timeit.timeit进行了对比,可见repeat只是重复了多次而已

在这里插入图片描述


2.3 使用timeit.default_timer(),获取计时器,默认是 time.perf_counter(),示例:
在这里插入图片描述

备注:
两个计时器—— time.perf_counter()和time.process_time()
time.perf_counter()
返回性能计数器的值(以小数秒为单位),即具有最高分辨率的时钟测量一段很短的时间。它确实包括睡眠期间经过的时间,并且是全系统的.返回值的引用点未定义,因此只有连续调用的结果之间的差异是有效的。
time.process_time():
返回系统和当前进程的用户CPU时间之和的值(以小数秒为单位)。它不包括睡眠期间经过的时间。它的定义是全过程的。
返回值的引用点未定义,因此只有连续调用的结果之间的差异是有效的。

在一些背景下,“时间”有两种不同的类型:绝对时间和相对时间。
绝对时间是“真实世界时间”,由time.time()我们都习惯于处理这个问题。它通常是从过去的一个不动点(例如,01/01/1970 UTC的UNIX时代)以至少1秒的分辨率来测量的。现代系统通常提供毫秒或微秒分辨率。它由大多数计算机上的专用硬件维护,RTC(实时时钟)电路通常由电池供电,因此系统能够实时跟踪电源之间的变化。这个“真实世界时间”也会根据你的位置(时区)和季节(夏时制)进行修改,或者表示为与UTC(也被称为格林尼治标准时间或祖鲁时间)的抵消。
第二,有相对时间,由time.perf_counter和time.process_time。这种类型的时间与现实世界的时间没有明确的关系,从某种意义上说,这种关系是系统和实现特定的。它只能用于测量时间间隔,即与两个瞬间之间的时间成正比的单位数。这主要用于评估相对性能(例如,此版本的代码运行速度是否比代码的版本更快)。
在现代系统中,它是用CPU计数器测量的,它在与CPU硬件时钟相关的频率上单调增加。计数器的分辨率高度依赖于系统的硬件,在大多数情况下,它的值不能可靠地与现实世界的时间相关,甚至无法在系统间进行比较。此外,每次启动或重置CPU时,计数器值都会被重置。
time.perf_counter返回计数器的绝对值。time.process_time是一个值,该值从CPU计数器派生而来,但仅在给定进程在CPU上运行时才更新,可以细分为“用户时间”,即进程本身在CPU上运行的时间,以及“系统时间”,即操作系统内核代表进程在CPU上运行的时间。

//参考https://stackoverflow.com/questions/25785243/understanding-time-perf-counter-and-time-process-time


2.4 使用timeit.Timer类来进行时间计算
timeit.Timer(stmt='pass', setup='pass', timer=<timer function>, globals=None)

  • stmt 语句,要执行的表达式,多个语句可以使用;分开
  • setup 语句,只在第一次初始化时执行的表达式,在之后会跳过
  • timer 计时器,默认是time.perf_counter()
  • globals 全局变量,需要是个字典

2.4.1 Timer的timeit方法
timeit(number=1000000)
将构造函数中的stmt语句执行number遍

2.4.2 Timer的repeat方法
repeat(repeat=5, number=1000000)
重复计时repeat次

2.4.3 Timer的autorange方法
autorange(callback=None)
自动确定要调用多少次timeit(),它调用timeit()重复使总时间>=0.2秒,返回最终的循环数(循环数,该循环数所需的时间)。它调用timeit()的次数取自从序列1,2,5,10,20,50…,.直到所用的时间至少是0.2秒。
如果callback不为空,在每次调用完都会执行callback(number, time_taken)

2.4.4 Timer的print_exc方法
print_exc(file=None)
打印错误信息,默认打印到sys.stderr
示例:
在这里插入图片描述

2.4.5 Timer综合使用
程序计算阶乘,在初始构造时,传入的参数stmt为factorial是每次都执行的语句,而from test_timeit import factorial;x=n;x += 10;只在初始化时执行一次,语句间使用了;作为分隔,同时还传入了全局变量n。之后打印输出执行1000遍的时间,而后打印重复10次、每次执行10000遍的时间,之后通过构造lambda表达式作为callback,使用autorange自动执行,直到时间达到0.2s,每次执行到一定的次数都会调用callback,打印number和time_taken。具体代码如下:

import timeit

def factorial(n):
    if n == 1: return 1
    return n * factorial(n-1)

if __name__ == "__main__":
    n = 10
    t = timeit.Timer("factorial(x)", "from test_timeit import factorial;x=n;x += 10;", globals={'n': n})
    print(t.timeit(1000))
    print(t.repeat(10, 10000))
    callback = lambda number, time_taken: print("number:%d, time_taken:%f"%(number, time_taken))
    t.autorange(callback)

在这里插入图片描述

返回目录


3. timeit在python3.7和python2中的区别

  • python3.7中的timeit命令行执行时默认的重复次数是5,而python2是3
  • python2命令行执行时没有-p -u选项
  • python2导入执行时,timeit.timeit()没有globals参数
  • python2没有autorange()

python3.7的timeit使用体验比python2的timeit好,实际上除了timeit之外的其它很多方面也是,现在的学习之路打算全部转向python3了。
返回目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值