文档章节

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

o
 osc_w9s1w4o0
发布于 2019/03/28 19:06
字数 3163
阅读 11
收藏 0

「深度学习福利」大神带你进阶工程师,立即查看>>>

前言

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

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

<span id="c"></span>

目录

  • <a href="">一、使用timeit计算程序耗时</a>

    • <a href="#c-1">1. 命令行执行</a>

    • <a href="#c-2">2. 导入使用</a>

    • <a href="#c-3">3. timeit在python3.7和python2中的区别</a>

一、使用timeit计算程序耗时

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

<span id="c-1"></span>

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

<a href="#c">返回目录</a>


<span id="c-2"></span>

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)

在这里插入图片描述

<a href="#c">返回目录</a>


<span id="c-3"></span>

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了。 <a href="#c">返回目录</a>

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
Flappy Bird(安卓版)逆向分析(一)

更改每过一关的增长分数 反编译的步骤就不介绍了,我们直接来看反编译得到的文件夹 方法1:在smali目录下,我们看到org/andengine/,可以知晓游戏是由andengine引擎开发的。打开/res/raw/at...

enimey
2014/03/04
6.1K
18
CDH5: 使用parcels配置lzo

一、Parcel 部署步骤 1 下载: 首先需要下载 Parcel。下载完成后,Parcel 将驻留在 Cloudera Manager 主机的本地目录中。 2 分配: Parcel 下载后,将分配到群集中的所有主机上并解压缩。 3 激...

cloud-coder
2014/07/01
6.8K
1
Swift百万线程攻破单例(Singleton)模式

一、不安全的单例实现 在上一篇文章我们给出了单例的设计模式,直接给出了线程安全的实现方法。单例的实现有多种方法,如下面: class SwiftSingleton { } 这段代码的实现,在shared中进行条...

一叶博客
2014/06/20
3.5K
16
beego API开发以及自动化文档

beego API开发以及自动化文档 beego1.3版本已经在上个星期发布了,但是还是有很多人不了解如何来进行开发,也是在一步一步的测试中开发,期间QQ群里面很多人都问我如何开发,我的业余时间实在...

astaxie
2014/06/25
2.7W
22
树莓派(Raspberry Pi):完美的家用服务器

自从树莓派发布后,所有在互联网上的网站为此激动人心的设备提供了很多有趣和具有挑战性的使用方法。虽然这些想法都很棒,但树莓派( RPi )最明显却又是最不吸引人的用处是:创建你的完美家用...

异次元
2013/11/09
7.1K
8

没有更多内容

加载失败,请刷新页面

加载更多

程序员职场:拥有一个学位将会在你的职业生涯中更加顺利!

1、作为程序员为什么要拥有学位? 很多情况下,作为程序员,学位是进入大公司的敲门砖。 现在很多大的科技公司,学位是硬性要求。 一般都是本科以上的学历,甚至有的必须是硕士以上学历。 如...

IT技术分享社区
03/03
0
0
varchar和nvarchar有什么区别? - What is the difference between varchar and nvarchar?

问题: Is it just that nvarchar supports multibyte characters? 只是nvarchar支持多字节字符吗? If that is the case, is there really any point, other than storage concerns, to us......

技术盛宴
18分钟前
5
0
用flutter给图片加个好看的遮罩层【flutter20个实例之六】

一、老套路,先看样式 左起图一是我业务中的样式,左起图二、三是下方源码展示样式(复制可直接运行,无额外组件引入) 二、讲解 1.结构拆分 我们先看下页面布局结构,首先肯定是有个GridVie...

一代码农码一代
19分钟前
9
0
世界上最美的瀑布在这里,太美了!

亲近大自然,高山流水遇知音,倾听心灵的声音。。。 声明:文章及图片、视频来自网络,如有版权方面的疑问请和我们联系,我们将在24小时内删除。 本文分享自微信公众号 - Python提升课堂(DJXY0...

花儿开放
2014/08/17
0
0
商城小程序制作流程

随着商城小程序的火爆,很多商家都迫不及待的想制作商城小程序,下面就和大家分享一下商城小程序制作流程? 第1步: 注册并认证小程序账号 注册并认证小程序账号,打开百度搜索,“微信公众平...

木鱼小铺小程序1
29分钟前
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部