分析并优化你的python代码

Profiling and optimizing your Python code

性能分析

  • 只要找到性能瓶颈,采用更好的算法和合适的工具,大多数情况下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下工作。
  • 在程序运行结束时,它给出了一个详细的树状视图,而且是可交互的:

    16180222_LkvE.png

  • 对于一个长期运行的程序如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

优化资源

转载于:https://my.oschina.net/mickelfeng/blog/902169

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值