分析并优化你的python代码

Profiling and optimizing your Python code

[Are you losing your time in a loop?](https://popkey.co/u/q4omg?ref=embed)

性能分析

  • 只要找到性能瓶颈,采用更好的算法和合适的工具,大多数情况下Python就足以满足我们的生产环境需求。

  • 通过查看源码来找到程序缓慢的原因是低效的,即使像下面的例子那样微不足道的代码也可能是一个难题:

"""Sorting a large, randomly generated string and writing it to disk"""
import random


def write_sorted_letters(nb_letters=10**7):
    random_string = ''
    for i in range(nb_letters):
        random_string += random.choice('abcdefghijklmnopqrstuvwxyz')
    sorted_string = sorted(random_string)

    with open("sorted_text.txt", "w") as sorted_text:
        for character in sorted_string:
            sorted_text.write(character)

write_sorted_letters()
  • 瓶颈显然是磁盘访问,对吧?好,让我们用性能分析器看看。
  • 命令行运行:
python -m cProfile -s tottime your_program.py
  • 结果如下:
         40000054 function calls in 11.362 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
 10000000    4.137    0.000    5.166    0.000 random.py:273(choice)
        1    3.442    3.442   11.337   11.337 sort.py:5(write_sorted_letters)
        1    1.649    1.649    1.649    1.649 {sorted}
 10000000    0.960    0.000    0.960    0.000 {method 'write' of 'file' objects}
 10000000    0.547    0.000    0.547    0.000 {method 'random' of '_random.Random' objects}
 10000000    0.482    0.000    0.482    0.000 {len}
        1    0.121    0.121    0.121    0.121 {range}
        1    0.021    0.021   11.362   11.362 sort.py:1(<module>)
...
  • -s tottime使得结果按总花费时间排序。头几个就是耗时大户。
  • 所以看tottime列,我们发现,random模块的choice()函数几乎占用了总运行时间的三分之一。
  • 在我们优化之前,再进一步剖析下。
有的放矢
  • 以上的命令会分析你的整个程序,如果你想要更精确,用下面的代码段包裹住你想要分析的地方:
import cProfile
cp = cProfile.Profile()
cp.enable()

cp.disable()
cp.print_stats()

输出和之前类似,但减少了不必要的干扰。

  • 由于很难知道程序运行情况,一般策略是先分析整个程序,然后逐步缩小分析区域。
  • 更多关于cProfile和Profile模块的信息看这里
逐行分析
  • 有时,我们需要逐行分析代码,我们可以使用line_profiler
    ,安装:
    pip install line_profiler
    然后用@profile装饰我们要分析的函数:
@profile
def write_sorted_letters(nb_letters=10**7):
    ...

再在命令行运行:

kernprof -l -v your_program.py
  • -l 用于逐行分析
  • -v 用于立刻显示结果
  • 结果如下:
Total time: 21.4412 s
File: ./sort.py
Function: write_sorted_letters at line 5

Line #      Hits         Time    Per Hit   % Time  Line Contents
================================================================
     5                                             @profile
     6                                             def write_sorted_letters(nb_letters=10**7):
     7         1            1        1.0      0.0      random_string = ''
     8  10000001      3230206        0.3     15.1      for _ in range(nb_letters):
     9  10000000      9352815        0.9     43.6          random_string += random.choice('abcdefghijklmnopqrstuvwxyz')
    10         1      1647254  1647254.0      7.7      sorted_string = sorted(random_string)
    11                                           
    12         1         1334     1334.0      0.0      with open("sorted_text.txt", "w") as sorted_text:
    13  10000001      2899712        0.3     13.5          for character in sorted_string:
    14  10000000      4309926        0.4     20.1              sorted_text.write(character)
  • 要注意的是这个分析工具使得我们的程序慢了近一倍,但我们看到了每一行对性能的影响。
大型多线程web应用的分析
  • 上面的工具对单线程本地开发的性能分析足够简单有效,但是应对大型多线程应用就很不一样了,这时我们需要非常赞的Profiling module
  • sudo pip install profiling安装,profiling your_program.py运行。要记得移除@profile,那只会在line_profiler下工作。
  • 在程序运行结束时,它给出了一个详细的树状视图,而且是可交互的:


  • 对于一个长期运行的程序如web服务器,你需要这样启动它来及时地查看性能分析:
profiling live-profile your_server_program.py

性能分析资源

优化

  • 现在我们知道了程序是怎么占用cpu的,可以相应地优化它们。

小警告:
你应当只在必要时进行优化,因为优化后的代码的可读性和可维护性一般都会差上不少。
优化是可维护性和性能的交换。

救场的numpy
  • 看起来random.choice函数让我们变慢不少。
  • 让我们用鼎鼎大名的numpy库的相似函数来替换它,参数略有不同:
"""Sorting a large, randomly generated string and writing it to disk"""
from numpy import random


def write_sorted_letters(nb_letters=10**7):
    letters = tuple('abcdefghijklmnopqrstuvwxyz')

    random_letters = random.choice(letters, nb_letters)
    random_letters.sort()

    sorted_string = random_letters.tostring()

    with open("sorted_text.txt", "w") as sorted_text:
        for character in sorted_string:
            sorted_text.write(character)

write_sorted_letters()
  • Numpy的数值函数强大而快速,甚至可以并行处理。没有的话使用pip install numpy安装。
  • 让我们看看最新的性能分析结果:
         10011861 function calls (10011740 primitive calls) in 3.357 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
 10000000    1.272    0.000    1.272    0.000 {method 'write' of 'file' objects}
        1    1.268    1.268    3.321    3.321 numpy_sort.py:5(write_sorted_letters)
        1    0.657    0.657    0.657    0.657 {method 'sort' of 'numpy.ndarray' objects}
        1    0.120    0.120    0.120    0.120 {method 'choice' of 'mtrand.RandomState' objects}
        4    0.009    0.002    0.047    0.012 __init__.py:1(<module>)
        1    0.003    0.003    0.003    0.003 {method 'tostring' of 'numpy.ndarray' objects}
...
  • 很棒,快了3倍左右(3.3s vs 11.362s)
  • 现在,tottime时间列上,读写操作是最大的瓶颈了。让我们解决它,替换
with open("sorted_text.txt", "w") as sorted_text:
    for character in sorted_string:
        sorted_text.write(character)

with open("sorted_text.txt", "w") as sorted_text:
    sorted_text.write(sorted_string)

这避免了一个字符一个字符地写入磁盘,而是一次性写入整个字符串,利用磁盘缓存和缓冲区加速文件写入。

  • 最后,简单地统计我们的代码的运行时间:
time python your_program.py

输出为:

real 0m0.874s
user 0m0.852s
sys  0m0.280s

只花了1秒不到!

其他性能技巧

  • 请记住计算机中的这些延迟数:
 Latency Comparison Numbers
--------------------------
L1 cache reference                           0.5 ns
Branch mispredict                            5   ns
L2 cache reference                           7   ns                      14x L1 cache
Mutex lock/unlock                           25   ns
Main memory reference                      100   ns                      20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy             3,000   ns        3 us
Send 1K bytes over 1 Gbps network       10,000   ns       10 us
Read 4K randomly from SSD*             150,000   ns      150 us          ~1GB/sec SSD
Read 1 MB sequentially from memory     250,000   ns      250 us
Round trip within same datacenter      500,000   ns      500 us
Read 1 MB sequentially from SSD*     1,000,000   ns    1,000 us    1 ms  ~1GB/sec SSD, 4X memory
Disk seek                           10,000,000   ns   10,000 us   10 ms  20x datacenter roundtrip
Read 1 MB sequentially from disk    20,000,000   ns   20,000 us   20 ms  80x memory, 20X SSD
Send packet CA->Netherlands->CA    150,000,000   ns  150,000 us  150 ms

来自Latency Numbers Every Programmer Should Know

优化资源

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

推荐阅读更多精彩内容