python性能解决_我们如何发现并解决Python代码中性能下降的问题
Python部落(python.freelycode.com)組織翻譯,禁止轉(zhuǎn)載,歡迎轉(zhuǎn)發(fā)。
作者:Omer Lachish
最近,我們已經(jīng)開(kāi)始使用RQ庫(kù)代替Celery庫(kù)作為我們的任務(wù)運(yùn)行引擎。第一階段,我們只遷移了那些不直接進(jìn)行查詢(xún)工作的任務(wù)。這些任務(wù)包括發(fā)送電子郵件,確定哪些查詢(xún)需要刷新、記錄用戶(hù)事件和其他維護(hù)工作。
在部署完成之后我們注意到,任務(wù)數(shù)量相同的情況下,與Celery庫(kù)相比,RQ庫(kù)需要更多的CPU去執(zhí)行這些任務(wù)。我想我應(yīng)該分享一下我是如何分析和解決這個(gè)問(wèn)題的。
關(guān)于Celery庫(kù)和RQ庫(kù)的區(qū)別
Celery庫(kù)和RQ庫(kù)都有工作進(jìn)程的概念,而且都使用分支來(lái)允許多種任務(wù)并行運(yùn)行。當(dāng)你啟動(dòng)一個(gè)Celery工作進(jìn)程時(shí),它會(huì)分到幾個(gè)不同的進(jìn)程,每個(gè)進(jìn)程會(huì)自主地處理任務(wù)。使用RQ庫(kù),一個(gè)主進(jìn)程將只會(huì)實(shí)例化一個(gè)子進(jìn)程(稱(chēng)為“Worker Horse”),該子進(jìn)程在執(zhí)行完一個(gè)單獨(dú)的任務(wù)后將會(huì)結(jié)束。當(dāng)主進(jìn)程從隊(duì)列中提取另一項(xiàng)任務(wù)時(shí),它將會(huì)派出一個(gè)新的Work Horse。
在RQ庫(kù)中,通過(guò)使用更多的工作進(jìn)程,即可實(shí)現(xiàn)與Celery相同的并行性。 但是,Celery庫(kù)和RQ庫(kù)之間存在著細(xì)微的區(qū)別:Celery進(jìn)程在啟動(dòng)時(shí)會(huì)實(shí)例化多個(gè)子進(jìn)程,并將其重用于多個(gè)任務(wù)。 而使用RQ庫(kù)時(shí),你必須給每一個(gè)任務(wù)分配進(jìn)程。 兩種方法都有優(yōu)點(diǎn)和缺點(diǎn),但這些內(nèi)容不在本文討論范圍之內(nèi)。
標(biāo)桿分析法
在介紹任何內(nèi)容之前,我想要確定一個(gè)基準(zhǔn),即一個(gè)工作容器處理1000項(xiàng)任務(wù)需要多長(zhǎng)時(shí)間。我決定把重心放在record_event工作上,因?yàn)樗且环N頻繁的,輕量級(jí)的操作。我使用time命令來(lái)衡量性能,這里需要修改一下源代碼:為了得出完成1000項(xiàng)任務(wù)所需的時(shí)間,我傾向于采用RQ庫(kù)的burst模式,該模式在處理完作業(yè)后會(huì)退出流程。
我想避免測(cè)量那些被安排在基準(zhǔn)測(cè)試時(shí)間段的任務(wù)。因此,在task/general.py中的record_event聲明上方,通過(guò)將@job('default')替換為@job('benchmark'),可以將record_event移至一個(gè)名為benchmark的專(zhuān)用隊(duì)列。
現(xiàn)在我們可以開(kāi)始計(jì)時(shí)了。首先,我想查看一個(gè)進(jìn)程啟動(dòng)和停止需要多長(zhǎng)時(shí)間(沒(méi)有任何工作)以便之后可以從任何結(jié)果中減去該時(shí)間:
在我的計(jì)算機(jī)上,進(jìn)程初始化需要14.7秒。我會(huì)記住這個(gè)時(shí)間。
然后,我將1000個(gè)虛擬的record_event任務(wù)添加進(jìn)benchmark隊(duì)列中:
現(xiàn)在,運(yùn)行相同的命令,看看處理1000項(xiàng)任務(wù)需要多長(zhǎng)時(shí)間:
減去14.7秒的啟動(dòng)時(shí)間,我們看到4個(gè)進(jìn)程處理1000項(xiàng)任務(wù)需要102秒。現(xiàn)在,讓我們嘗試找出原因!為此,在進(jìn)程工作的同時(shí),我們將使用py_spy模塊。
分析
讓我們?cè)僭黾?000項(xiàng)任務(wù)(因?yàn)樯洗蔚臏y(cè)試已經(jīng)刪除了所有任務(wù)),運(yùn)行進(jìn)程并同時(shí)監(jiān)控它們所耗費(fèi)的時(shí)間:
我知道,最后一條命令非常短。 理想情況下,出于可讀性考慮,我會(huì)在每個(gè)“ &&”上都打斷該命令,但是這些命令應(yīng)該在同一個(gè)docker-compose exec worker bash session中按順序運(yùn)行。所以,以下是其功能的快速分析:在后臺(tái)中,burst模式下,開(kāi)啟了4個(gè)進(jìn)程。
等待15秒(大致讓它們完成啟動(dòng)。
安裝py-spy模塊。
運(yùn)行rq-info命令,并且為其中一個(gè)進(jìn)程進(jìn)行分層控制。
在該控制過(guò)程中記錄10秒中的活動(dòng)情況并將其保存到profile.svg文件中。
結(jié)果如下火焰圖所示:
從火焰圖中,我注意到record_event在sqlalchemy.orm.configure_mappers中花費(fèi)了很大一部分的執(zhí)行時(shí)間,并且每次處理一項(xiàng)工作時(shí)這種情況都會(huì)出現(xiàn)。從它們的文檔當(dāng)中,我知道了:初始化到目前為止已構(gòu)建的所有映射器的映射器間關(guān)系。
的確不需要在每一個(gè)分支上都發(fā)生這種事情。所以,我們可以在主進(jìn)程當(dāng)中一次性地初始化這些關(guān)系,避免在多個(gè)子進(jìn)程當(dāng)中重復(fù)性這些工作。
因此,在啟動(dòng)進(jìn)程之前,我已經(jīng)對(duì)sqlalchemy.org.configure_mappers()進(jìn)行了調(diào)用,并再次進(jìn)行了測(cè)試:
如果我們減去14.7秒的啟動(dòng)時(shí)間,4個(gè)線程處理1000項(xiàng)任務(wù)的時(shí)間將從102秒減少到24.6秒。 比以前提高了4倍! 通過(guò)修復(fù)此程序,我們成功地將RQ生產(chǎn)資源減少了4倍,并保持了相同的吞吐量。
我認(rèn)為,你應(yīng)該記住,在單線程和多線程的情況下,應(yīng)用的行為是有所不同的。如果每一項(xiàng)任務(wù)沒(méi)有繁重的重復(fù)的工作要做,通常最好在分到多個(gè)線程之前進(jìn)行一次。這些事情在測(cè)試和開(kāi)發(fā)過(guò)程中不會(huì)出現(xiàn),因此請(qǐng)確保您進(jìn)行充分的測(cè)試并挖掘出任何會(huì)出現(xiàn)的性能問(wèn)題。英文原文:https://blog.redash.io/how-we-spotted-and-fixed-a-performance-degradation-in-our-python-code/
譯者:Lyx
總結(jié)
以上是生活随笔為你收集整理的python性能解决_我们如何发现并解决Python代码中性能下降的问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: springboot 整合mybatis
- 下一篇: 安康看免疫性不孕症最好的医院推荐