IPython功能(7)Profiling和Timing代码

为了提高编码效率,有时候需要检查单个或一系列代码的运行时间,这时候就可以用的IPython的一些相关命令,
下面我们会来讨论以下命令:
%time: 单个指令的运行时间
%timeit: 重复执行单个指令以获得更准确的时间
%prun: 运行代码并给出分析
%lprun: 运行代码并给出逐行分析
%memit: 单个指令的内存分析
%mprun: 运行代码的内存分析

最后4个不是IPython自带的,需要安装line_profiler和memory_profiler这2个扩展包。

代码片段计时:%timeit和%time

%timeit是行magic函数,%%timeit是单元magic函数,可以在IPython Magic Commands 查看函数功能,它们可以计算重复执行代码的时间。

%timeit sum(range(100))
100000 loops, best of 3: 1.54 µs per loop

请注意,由于此操作非常快,%timeit 会自动进行大量重复。
对于较慢的命令,%timeit 会自动调整并执行更少的重复:

%%timeit
total = 0
for i in range(1000):
    for j in range(1000):
        total += i * (-1) ** j
1 loops, best of 3: 407 ms per loop

有时重复不是最好的选择,我们可能会被重复操作误导,对预先排序进行排序比对未排序列表进行排序要快得多,因此重复会使结果发生偏差:

import random
L = [random.random() for i in range(100000)]
%timeit L.sort()
100 loops, best of 3: 1.9 ms per loop

这种情况用%time函数比较合适,长代码也同样适用,因为此时短的,系统性的延迟不会对结果产生影响。
我们先来看一下对预先排序进行排序比对未排序列表进行排序的时间:

import random
L = [random.random() for i in range(100000)]
print("sorting an unsorted list:")
%time L.sort()
sorting an unsorted list:
CPU times: user 40.6 ms, sys: 896 µs, total: 41.5 ms
Wall time: 41.5 ms
print("sorting an already sorted list:")
%time L.sort()
sorting an already sorted list:
CPU times: user 8.18 ms, sys: 10 µs, total: 8.19 ms
Wall time: 8.24 ms

可以看到预先排序的列表排起来很快,也可以看到 %time 花的时间比 %timeit 多很多,甚至对于预先排序的列表。因为%timeit在后台做了一些聪明的事情来防止系统调用干扰计时。

对于 %time 和 %timeit,双百分号可以对多行代码进行计时:

%%time
total = 0
for i in range(1000):
    for j in range(1000):
        total += i * (-1) ** j
CPU times: user 504 ms, sys: 979 µs, total: 505 ms
Wall time: 505 ms

了解%time 和 %timeit 的更多信息可以看下帮助文档,比如在IPython输入%time?。

分析整个脚本:%prun

一个程序由许多单个语句组成,有时在上下文中对这些语句进行计时比单独对它们计时更重要。Python包含一个内置代码分析器(可以在Python文档中阅读),但IPython提供了一种更方便的方法来使用此分析器,就是使用magic函数 %prun。

为了举例,我们先定义一个可以进行简单计算的函数:

def sum_of_lists(N):
    total = 0
    for i in range(5):
        L = [j ^ (j >> i) for j in range(N)]
        total += sum(L)
    return total

可以用%prun看下分析结果:

%prun sum_of_lists(1000000)

如果是在notebook里,结果会输出在页面里,长得像这样:

14 function calls in 0.714 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        5    0.599    0.120    0.599    0.120 <ipython-input-19>:4(<listcomp>)
        5    0.064    0.013    0.064    0.013 {built-in method sum}
        1    0.036    0.036    0.699    0.699 <ipython-input-19>:1(sum_of_lists)
        1    0.014    0.014    0.714    0.714 <string>:1(<module>)
        1    0.000    0.000    0.714    0.714 {built-in method exec}

结果是一个表,该表按每次函数调用的总时间顺序指示执行花费最多时间的位置。在这种情况下,大部分执行时间都在sum_of_lists 中的列表理解中。从这里开始,我们可以开始考虑可以进行哪些更改来提高算法性能。

%prun? 查看更多帮助信息。

逐行分析:%lprun

%prun 分析逐个函数的时候很有用,但有时逐行分析更方便,但这个功能不是内置在Python或者IPython中,可以安装一个叫line_profiler的包执行此操作。首先用Python的打包工具pip安装此包:

$ pip install line_profiler

然后可以用IPython加载该包:

%load_ext line_profiler

现在可以用%lprun功能进行逐行分析:

%lprun -f sum_of_lists sum_of_lists(5000)

输出结果类似以下:

Timer unit: 1e-06 s

Total time: 0.009382 s
File: <ipython-input-19-fa2be176cc3e>
Function: sum_of_lists at line 1

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     1                                           def sum_of_lists(N):
     2         1            2      2.0      0.0      total = 0
     3         6            8      1.3      0.1      for i in range(5):
     4         5         9001   1800.2     95.9          L = [j ^ (j >> i) for j in range(N)]
     5         5          371     74.2      4.0          total += sum(L)
     6         1            0      0.0      0.0      return total

从以上结果中我们可以看到哪步程序花费的时间最多,以此为依据改进脚本。

同样%lprun? 查看帮助文档。

存储分析 %memit 和 %mprun

同样memory_profiler需要从外部安装:

$ pip install memory_profiler

IPython加载此包:

%load_ext memory_profiler

memory_profiler包含2个有用的magic函数:%memit 函数(提供了一个相当于 %timeit 的内存分析)和 %mprun 函数(提供了一个相当于 %lprun 的内存分析)。
%memit 用起来比较简单:

%memit sum_of_lists(1000000)
peak memory: 100.08 MiB, increment: 61.36 MiB

逐行代码的内存占用描述可以用 %mprun,但是可惜的是这个magic命令只能对单独模块里定义的function起作用(不能直接应用于notebook)。所以我们先用%%file函数定义一个简单的模块叫做mprun_demo.py,这个脚本里包含我们定义的sum_of_lists功能,
先写一下我们的mprun_demo.py脚本:

%%file mprun_demo.py
def sum_of_lists(N):
    total = 0
    for i in range(5):
        L = [j ^ (j >> i) for j in range(N)]
        total += sum(L)
        del L # remove reference to L
    return total

然后导入此功能并运行内存分析器:

from mprun_demo import sum_of_lists
%mprun -f sum_of_lists sum_of_lists(1000000)

输出的结果看起来像这样:

Filename: ./mprun_demo.py

Line #    Mem usage    Increment   Line Contents
================================================
     4     71.9 MiB      0.0 MiB           L = [j ^ (j >> i) for j in range(N)]


Filename: ./mprun_demo.py

Line #    Mem usage    Increment   Line Contents
================================================
     1     39.0 MiB      0.0 MiB   def sum_of_lists(N):
     2     39.0 MiB      0.0 MiB       total = 0
     3     46.5 MiB      7.5 MiB       for i in range(5):
     4     71.9 MiB     25.4 MiB           L = [j ^ (j >> i) for j in range(N)]
     5     71.9 MiB      0.0 MiB           total += sum(L)
     6     46.5 MiB    -25.4 MiB           del L # remove reference to L
     7     39.1 MiB     -7.4 MiB       return total

Increment这一列告诉我们每一行对总内存的影响:可以看到我们创建和删除L列表,增加和25MB的内存使用。最上面那行是Python编译器自己占用的内存。

同样可以用 %memit? 和%mprun? 查看更多信息。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容