0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

能快速找到代码运行最慢部分的编程神器

Linux爱好者 来源:Python七号 作者:somenzz 2021-10-13 16:40 次阅读

天下武功,唯快不破。

编程也不例外,你的代码跑的快,你能快速找出代码慢的原因,你的码功就高。

今天分享一个超级实用的 Python 性能分析工具 pyinstrument ,可以快速找到代码运行最慢的部分,帮助提高代码的性能。支持 Python 3.7+ 且能够分析异步代码,仅需一条命令即可显示具体代码的耗时。经常写 Python 的小伙伴一定要用一下。

安装

pipinstallpyinstrument

简单的使用

在程序的开始,启动 pyinstrument 的 Profiler,结束时关闭 Profiler 并打印分析结果如下:

frompyinstrumentimportProfiler

profiler=Profiler()
profiler.start()

#这里是你要分析的代码

profiler.stop()

profiler.print()

比如这段代码 123.py,我们可以清楚的看到是列表推导式比较慢:

frompyinstrumentimportProfiler

profiler=Profiler()
profiler.start()

#这里是你要分析的代码
a=[iforiinrange(100000)]
b=(iforiinrange(100000))

profiler.stop()
profiler.print()

上述分析需要修改源代码,如果你使用命令行工具,就不需要修改源代码,只需要执行 pyinstrument xxxx.py 即可:

比如有这样一段排序的程序 c_sort.py:

importsys
importtime

importnumpyasnp

arr=np.random.randint(0,10,10)

defslow_key(el):
time.sleep(0.01)
returnel

arr=list(arr)

foriinrange(10):
arr.sort(key=slow_key)

print(arr)

这段代码里面故意放了一句 time.sleep(0.01) 来延迟性能,看看 pyinstrument 能否识别,命令行执行 pyinstrument c_sort.py:

从结果来看,程序运行了 1.313 秒,而 sleep 就运行了 1.219 秒,很明显是瓶颈,现在我们把它删除,再看看结果:

删除之后,性能最慢的就是 numpy 模块的初始化代码 __init__.py了,不过这些代码不是自己写的,而且并不是特别慢,就不需要去关心了。

分析 Flask 代码

Web 应用也可以使用这个来找出性能瓶颈,比如 flask,只需要在请求之前记录时间,在请求之后统计时间,只需要在 flask 的请求拦截器里面这样写:

fromflaskimportFlask,g,make_response,request
app=Flask(__name__)

@app.before_request
defbefore_request():
if"profile"inrequest.args:
g.profiler=Profiler()
g.profiler.start()

@app.after_request
defafter_request(response):
ifnothasattr(g,"profiler"):
returnresponse
g.profiler.stop()
output_html=g.profiler.output_html()
returnmake_response(output_html)

假如有这样一个 API:

@app.route("/dosomething")
defdo_something():
importrequests
requests.get("http://google.com")
return"Googlesayshello!"

为了测试这个 API 的瓶颈,我们可以在 url 上加一个参数 profile 就可以:http://127.0.0.1:5000/dosomething?profile,哪一行代码执行比较慢,结果清晰可见:

分析 Django 代码

分析 Django 代码也非常简单,只需要在 Django 的配置文件的 MIDDLEWARE 中添加

"pyinstrument.middleware.ProfilerMiddleware",

然后就可以在 url 上加一个参数 profile 就可以:

如果你不希望所有人都能看到,只希望管理员可以看到,settings.py 可以添加这样的代码:

defcustom_show_pyinstrument(request):
returnrequest.user.is_superuser

PYINSTRUMENT_SHOW_CALLBACK="%s.custom_show_pyinstrument"%__name__

如果不想通过 url 后面加参数的方式查看性能分析,可以在 settings.py 文件中添加:

PYINSTRUMENT_PROFILE_DIR='profiles'

这样,每次访问一次 Django 接口,就会将分析结果以 html 文件形式保存在 项目目录下的 profiles 文件夹中。

分析异步代码

简单的异步代码分析:

async_example_simple.py:

importasyncio

frompyinstrumentimportProfiler

asyncdefmain():
p=Profiler()
withp:
print("Hello...")
awaitasyncio.sleep(1)
print("...World!")
p.print()

asyncio.run(main())

复杂一些的异步代码分析:

importasyncio
importtime

importpyinstrument

defdo_nothing():
pass

defbusy_wait(duration):
end_time=time.time()+duration

whiletime.time()< end_time:
        do_nothing()

async def say(what, when, profile=False):
    ifprofile:
p=pyinstrument.Profiler()
p.start()

busy_wait(0.1)
sleep_start=time.time()
awaitasyncio.sleep(when)
print(f"sleptfor{time.time()-sleep_start:.3f}seconds")
busy_wait(0.1)

print(what)
ifprofile:
p.stop()
p.print(show_all=True)

loop=asyncio.get_event_loop()

loop.create_task(say("firsthello",2,profile=True))
loop.create_task(say("secondhello",1,profile=True))
loop.create_task(say("thirdhello",3,profile=True))

loop.run_forever()
loop.close()

工作原理

Pyinstrument 每 1ms 中断一次程序,并在该点记录整个堆栈。它使用 C 扩展名和 PyEval_SetProfile 来做到这一点,但只每 1 毫秒读取一次读数。你可能觉得报告的样本数量有点少,但别担心,它不会降低准确性。默认间隔 1ms 是记录堆栈帧的下限,但如果在单个函数调用中花费了很长时间,则会在该调用结束时进行记录。如此有效地将这些样本“打包”并在最后记录。

Pyinstrument 是一个统计分析器,并不跟踪,它不会跟踪您的程序进行的每个函数调用。相反,它每 1 毫秒记录一次调用堆栈。与其他分析器相比,统计分析器的开销比跟踪分析器低得多。

比如说,我想弄清楚为什么 Django 中的 Web 请求很慢。如果我使用 cProfile,我可能会得到这个:

151940functioncalls(147672primitivecalls)in1.696seconds

Orderedby:cumulativetime

ncallstottimepercallcumtimepercallfilename:lineno(function)
10.0000.0001.6961.696profile:0(at0x1053d6a30,file"./manage.py",line2>)
10.0010.0011.6931.693manage.py:2()
10.0000.0001.5861.586__init__.py:394(execute_from_command_line)
10.0000.0001.5861.586__init__.py:350(execute)
10.0000.0001.1421.142__init__.py:254(fetch_command)
430.0130.0001.1240.026__init__.py:1()
3880.0080.0001.0620.003re.py:226(_compile)
1580.0050.0001.0480.007sre_compile.py:496(compile)
10.0010.0011.0421.042__init__.py:78(get_commands)
1530.0010.0001.0360.007re.py:188(compile)
106/1020.0010.0001.0300.010__init__.py:52(__getattr__)
10.0000.0001.0291.029__init__.py:31(_setup)
10.0000.0001.0211.021__init__.py:57(_configure_logging)
20.0020.0011.0110.505log.py:1()

看完是不是还是一脸懵逼,通常很难理解您自己的代码如何与这些跟踪相关联。Pyinstrument 记录整个堆栈,因此跟踪昂贵的调用要容易得多。它还默认隐藏库框架,让您专注于影响性能的应用程序/模块:

_.___/_______/_Recorded:1435Samples:131
/_//_////_///_/////_'///Duration:3.131CPUtime:0.195
/_/v3.0.0b3

Program:examples/django_example/manage.pyrunserver--nothreading--noreload

3.131manage.py:2
└─3.118execute_from_command_linedjango/core/management/__init__.py:378
[473frameshidden]django,socketserver,selectors,wsgi...
2.836selectselectors.py:365
0.126_get_responsedjango/core/handlers/base.py:96
└─0.126hello_worlddjango_example/views.py:4

最后的话

本文分享了 pyinstrument 的用法,有了这个性能分析神器,以后优化代码可以节省很多时间了,这样的效率神器很值得分享,毕竟人生苦短,能多点时间干点有意思的不香么?

责任编辑:haq


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 编程
    +关注

    关注

    88

    文章

    3439

    浏览量

    92375
  • 代码
    +关注

    关注

    30

    文章

    4555

    浏览量

    66739

原文标题:效率神器:快速定位运行最慢的代码

文章出处:【微信号:LinuxHub,微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    STM32G070RB部分代码运行是什么原因导致的?

    STM32G070RB 部分代码运行 主循环代码运行一会就不运行了,但定时器、串口都正常在工作
    发表于 04-02 06:55

    数控编程的g功能代码是什么

    数控编程中,G代码(也称为指令代码)是一种用于控制数控机床运动、辅助功能和工作过程的指令。在数控编程中,通过一系列的G代码指令的组合和排列,
    的头像 发表于 02-14 15:51 1063次阅读

    如何快速找到PCB中的GND?

    如何快速找到PCB中的GND?  在PCB设计和制作过程中,找到地线(GND)是非常关键的步骤。GND是电子电路中一个非常重要的参考面,它提供了电路中的参考电平,并在电路中提供低噪声的当前路径。因此
    的头像 发表于 02-03 17:04 1326次阅读

    如何在运行时擦除/编程UCB?

    我有一个项目需要在现场更新 SOTA。 这需要在运行时更新必要的 UCB 值。 我已经看过擦除/编程 PFLASH 和 DFLASH 的示例代码,但我还没有找到任何特定于 UCB 内存
    发表于 01-22 07:18

    串联谐振怎么使用才能快速找到谐振点

    串联谐振怎么使用才能快速找到谐振点  串联谐振是指通过串联电路中的电感和电容来实现谐振的现象。谐振是一个非常重要的电路现象,在无线电通信领域、音频信号处理领域等都有广泛的应用。在设计和调整串联谐振
    的头像 发表于 12-20 15:35 683次阅读

    如何快速找到PCB中的GND?

    如何快速找到PCB中的GND?
    的头像 发表于 12-07 14:26 622次阅读
    如何<b class='flag-5'>快速</b><b class='flag-5'>找到</b>PCB中的GND?

    python软件怎么运行代码

    Python是一种高级编程语言,它被广泛用于开发各种类型的应用程序,从简单的脚本到复杂的网络应用和机器学习模型。要运行Python代码,您需要一个Python解释器,它可以将您的代码
    的头像 发表于 11-28 16:02 532次阅读

    python代码写完后点哪个运行

    当你完成了编写Python代码后,你可以选择多种方式来运行它。下面是几种常见的运行代码的方式: Python解释器:Python是一种解释型语言,你可以直接使用Python解释器来
    的头像 发表于 11-24 09:28 1166次阅读

    python如何换行而不运行代码

    在Python中,换行是一种用来增加代码的可读性和组织性的方式。当你在编写Python代码时,换行通常用于分隔不同的代码行或块,使其更易于阅读和理解。然而,并非所有的换行都会运行
    的头像 发表于 11-22 10:52 1251次阅读

    python怎样运行代码

    讨论Python代码运行方式,包括解释器、交互式环境和命令行。 Python代码可以通过两种主要的方式运行:解释执行和编译执行。解释执行是指将源
    的头像 发表于 11-22 10:31 637次阅读

    linux虚拟机怎么运行代码

    运行代码是Linux虚拟机中的常见操作,本文将详细介绍如何运行代码。 首先,要运行代码,你需要先
    的头像 发表于 11-17 10:12 2205次阅读

    Black:自动格式化代码神器

    代码规范啊! 没事,现在有Black这个神器,我们可以在终端中输入一句命令就自动格式化代码!而且对于初学者来说,用Black来学习代码规范也是一个非常不错的选择。 1.准备 开始之前,
    的头像 发表于 11-02 14:51 696次阅读
    Black:自动格式化<b class='flag-5'>代码</b>的<b class='flag-5'>神器</b>

    关于STM8S字长度问题和自动快速字节编程问题

    (1)字长度问题:RM0016文档 字编程部分介绍1字=4字节,PM0044介绍1字=2字节 (例如LDW指令),因此这两个文档不是矛盾吗? (2)自动快速字节编程问题:上文字节
    发表于 08-08 07:42

    KiCad拼版神器KiKit的安装与使用

    运行命令 “kikit --help”,如得到以下界面,说明安装成功: 4. 打开KiCad的“插件和工具管理器” 5. 找到Kikit,安装并应用。 6. 重启KiCad,如果在菜单或图标中看
    发表于 05-22 19:07

    在哪里可以找到关于Arduino主机上运行的NFC的示例代码

    我在哪里可以找到关于在 Arduino 主机上运行的 NFC 的示例代码? 例如 NCF3320 和 Arduino(atmega,尤其是...)。
    发表于 05-05 08:50