Python代码优化工具——line_profile

  • Post author:
  • Post category:python




一、工具介绍

line_profiler是Python的一个第三方库,其功能时基于函数的逐行代码分析工具。通过该库,可以对目标函数(允许分析多个函数)进行时间消耗分析,便于代码调优。



二、安装

使用命令

pip install line_profiler

进行安装。



三、分析结果注解

先放上这个工具的分析结果,各位朋友可以看一下是否满足自己的要求,再决定是否安装以及使用。

分析结果


部分参数注解:

  • Timer unit:分析表中,Time和Per Hit的数值单位,时间单位固定为秒(s),默认数值单位是1e-6,合在一起便是1e-6s,即微秒。实际某一行的运行时间是Time * Timer unit的值,觉得不好理解的话,下方介绍

    kernprof

    命令时有一个示例供各位同学理解,这里只是简单提一下。
  • Total time:当前函数的时间消耗,单位是秒。
  • File:当前函数所在文件名。
  • Function:当前函数的函数名以及在文件中的位置。
  • Line #:代码所在行号。
  • Hits:在执行过程中,该行代码执行次数,即命中数。
  • Time:在执行过程中,该行代码执行的总时间,默认单位是微秒。
  • Per Hit:在执行过程中,平均每次执行该行代码所耗时间,默认单位是微秒。
  • % Time:执行该行代码所耗总时间占执行当前函数所耗总时间的百分比。
  • Line Contents:该行代码的内容。


分析结果保存位置


待代码执行完毕后,会在

当前目录(注意,不是代码所在目录,而是执行命令的当前目录)

下生成一个后缀为

.lprof

的同名文件,该文件内存放的就是分析结果。



四、使用

line_profiler工具有三种使用姿势,各有利弊



1、使用

kernprof

命令进行分析



(1)使用姿势

在想要分析的函数前加上装饰器

@profile

,然后在终端数据使用命令

kernprof

进行分析。


github 网址


该命令的具体参数如下:

usage: kernprof [-h] [-V] [-l] [-b] [-o OUTFILE] [-s SETUP] [-v] [-u UNIT]
                [-z]
                script ...

Run and profile a python script.

positional arguments:
  script                The python script file to run
  args                  Optional script arguments

optional arguments:
  -h, --help            show this help message and exit
  -V, --version         show program's version number and exit
  -l, --line-by-line    Use the line-by-line profiler instead of cProfile.
                        Implies --builtin.
  -b, --builtin         Put 'profile' in the builtins. Use
                        'profile.enable()'/'.disable()', '@profile' to
                        decorate functions, or 'with profile:' to profile a
                        section of code.
  -o OUTFILE, --outfile OUTFILE
                        Save stats to <outfile> (default: 'scriptname.lprof'
                        with --line-by-line, 'scriptname.prof' without)
  -s SETUP, --setup SETUP
                        Code to execute before the code to profile
  -v, --view            View the results of the profile in addition to saving
                        it
  -u UNIT, --unit UNIT  Output unit (in seconds) in which the timing info is
                        displayed (default: 1e-6)
  -z, --skip-zero       Hide functions which have not been called
  • 简单来说,如果想用

    @profile

    装饰器的方式分析代码,就需要使用

    -l

    参数。
  • 默认分析结果会保存在

    .prof

    文件里,如果加了

    -l

    参数,则会将分析结果保存在

    .lprof

    文件里。
  • 可以使用

    python -m line_profiler <.lprof文件名>

    查看分析结果,此时不会再次执行代码,只是查询分析结果。
  • 如果需要在分析代码的同时打印分析结果,可以使用

    -v

    参数。
  • 默认情况下,

    @profile

    装饰了几个函数,就会出现几张分析表,如果某个函数没有被执行,则其Hit、Time等值显示为空。可以使用

    -z

    参数隐去未执行函数的分析结果。需要注意的是,

    -z

    参数只是在显示时隐藏了未执行函数的分析表,但不是不分析该函数,当使用

    python -m line_profiler <分析结果文件名>

    时,还是可以看到未执行函数的分析表的。
  • 想在分析代码之前,执行一些不加入分析的前置操作(比如配置环境变量等),可以使用

    -s <前置操作的.py文件名>

    ,这样就会在执行分析代码所在的文件之前,先执行一遍前置操作文件。
  • 想将分析结果保存到指定的文件,而不是默认的文件里,可以使用

    -o [<文件路径>/]<文件名>

    来指定。文件后缀不一定非要

    .prof

    或者

    .lprof

    ,经测试结果保存到

    .txt

    文件也是可以的,使用

    python -m line_profile <文件名.txt>

    同样可以显示结果。
  • 默认的时间数值单位,是1e-6,即微秒,我们可以通过

    -u <数值>

    来修改。以下是示例:函数功能是睡眠2秒,但因为Timer unit的数值单位不一样,其显示的数值也不一样,但代表的含是一样的。

    请添加图片描述



(2)示例:

代码如下,该示例共有三个函数,每个函数的功能都是睡眠一段时间后,构造10个随机数。

import random
import time

@profile
def function1():
    time.sleep(2)
    for _ in range(100):
        y = random.randint(1,100)


def function2():
    time.sleep(5)
    for _ in range(100):
        y = random.randint(1,100)


def function3():
    time.sleep(7)
    for _ in range(100):
        y = random.randint(1,100)

@profile
def main():
    function1()
    function2()
    function3()


if __name__ == "__main__":       
    main()

执行结果如下:

请添加图片描述

因为我只在

main



function1

函数加了装饰器,所以分析结果只有

main



function1

,实际使用时可以自行增加或减少函数。



(3)该姿势的利弊


优点

  • 不用在代码里导入第三方包,也不用修改代码的实际逻辑,有利于维持原代码的整体性。
  • 使用方便,只需在待分析的函数前加装饰器

    @profile

    即可。
  • 用于分析的函数可以不在同一个文件内,方便分析较为复杂、调用方法较多的程序。


缺点

  • 因为需要在所有待分析的函数前都需要加装饰器,所以添加和删除的麻烦度与待分析的函数的数量成正比。
  • 因为

    @profile

    并不是第三方导入的,所以正常运行代码时会报错。
  • 同样是因为

    @profile

    不是第三方导入的,在IDEA中该段代码会有代码错误的红色波浪线提示,强迫症震怒。



2、使用

python

命令进行分析



(1)使用姿势

需要在代码里引入第三方包

LineProfiler

,并在原代码里添加一些代码才可以实现,进行分析时就像平时执行代码一样

python <文件名>

即可。



(2)示例:

代码如下,该示例共有三个函数,每个函数的功能都是睡眠一段时间后,构造10个随机数。

import random
import time
from line_profiler import LineProfiler


def function1():
    time.sleep(2)
    for _ in range(100):
        y = random.randint(1,100)


def function2():
    time.sleep(5)
    for _ in range(100):
        y = random.randint(1,100)


def function3():
    time.sleep(7)
    for _ in range(100):
        y = random.randint(1,100)


def main():
    function1()
    function2()
    function3()


if __name__ == "__main__":
    lp = LineProfiler()  # 构造分析对象
    """如果想分析多个函数,可以使用add_function进行添加"""
    lp.add_function(function1)  # 添加第二个待分析函数
    lp.add_function(function2)  # 添加第三个待分析函数
    test_func = lp(main)  # 添加分析主函数,注意这里并不是执行函数,所以传递的是是函数名,没有括号。
    test_func()  # 执行主函数,如果主函数需要参数,则参数在这一步传递,例如test_func(参数1, 参数2...)
    lp.print_stats()  # 打印分析结果
    """
    坑点:
        1:test_func = lp(main)这一步,是实际分析的入口函数(即第一个被调用的函数,但不一定是main函数),所以这里封装的函数必须是测试脚本要执行的第一个函数。
        2:test_func()这一步才是真正执行,如果缺少这一步,代码将不会被执行
        3:lp.print_stats()这一步是打印分析结果,如果缺少这一步,将不会在终端看到分析结果。
        4:分析结果只与是否加入分析队列有关,而不管该函数是否被实际执行,如果该函数加入分析但没有被执行,则Hits、Time等值为空。
    """

执行结果如下:

请添加图片描述



(3)该姿势的利弊


优点

  • 添加/删除待分析函数方便,不用修改原代码,只需要在测试代码入口使用LineProfiler对象增加/删除待分析函数名即可。
  • 测试命令与平时运行代码的相同,不用增加学习负担。


缺点

  • 因为要导入第三方包以及创建LineProfiler对象,需要写一份专用于分析的测试脚本。
  • 分析多个函数时,是使用add_function+函数名来实现的,所以需要显示地导入待分析函数,或者将待分析函数拷贝到当前分析脚本,对较为复杂的逻辑很不友好。
  • 该姿势存在一些坑点,已在示例代码的备注中说明。
  • 不会将分析结果保存到.lprof文件中。



3、在Jupyter Notebook内使用line_profile

这一种姿势在实际工作中不常用,感兴趣的可以移步这位大佬的文章:

line_profiler: Line by Line Profiling of Python Code



版权声明:本文为weixin_42245157原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。