對(duì)代碼進(jìn)行調(diào)試和性能分析才能如魚得水

寫代碼怎樣才能如魚得水

寫代碼怎樣才能如魚得水取決于你寫代碼的態(tài)度,首先很多人在寫代碼的過(guò)程中可能真的只注重了寫代碼,沒(méi)有過(guò)多的思考,也不會(huì)對(duì)代碼進(jìn)行調(diào)試或性能上的分析,僅僅是為了完成或?qū)崿F(xiàn)某個(gè)功能而去寫代碼痹兜,這樣的寫代碼是沒(méi)有靈魂的,你不會(huì)得到絲毫的進(jìn)步颤诀,長(zhǎng)久下去也只能淪為碼農(nóng)而不是工程師字旭。

那么如何才能做到是事半功倍的編寫代碼,并能在每一次的開(kāi)發(fā)中得到提升和進(jìn)步呢崖叫?其實(shí)說(shuō)起來(lái)也不難遗淳,那就是每次對(duì)編寫的代碼進(jìn)行調(diào)試和性能分析,并深度思考進(jìn)行程序優(yōu)化提升心傀,以此來(lái)提升你的編寫能力屈暗,冰凍三尺非一日之寒,這事情雖然簡(jiǎn)單但是卻需要強(qiáng)大的執(zhí)行力脂男。但如果不想淪為碼農(nóng)或成為社畜养叛,就需要你的行動(dòng)了。接下來(lái)就為大家分享一下如何對(duì)代碼進(jìn)行調(diào)試和性能分析助你一臂之力宰翅。

代碼調(diào)試

說(shuō)到代碼調(diào)試弃甥,很多同學(xué)會(huì)問(wèn),那還不簡(jiǎn)單嗎汁讼?print()不就可以了嗎淆攻?你說(shuō)的對(duì),我們會(huì)在開(kāi)發(fā)的過(guò)程中大量使用print來(lái)對(duì)代碼進(jìn)行一些輸出測(cè)試嘿架,以確保程序是否達(dá)到預(yù)期瓶珊,這是一種非常簡(jiǎn)單粗暴的快速debug的方式,但是這種方法也僅限于小型程序耸彪。

我們會(huì)對(duì)每一個(gè)代碼塊進(jìn)行print輸出來(lái)調(diào)試是否達(dá)到預(yù)期伞芹,但是如果程序比較大,每次都需要重新運(yùn)行才能看到打印的結(jié)果搜囱,那成本肯定就很高丑瞧,特別還有一些通常需要反復(fù)運(yùn)行調(diào)試才能找到錯(cuò)誤的根源柑土,這樣如果僅僅使用print()打印那效率就很低了蜀肘。

當(dāng)然,除了使用print之外稽屏,我們還可以借助于IDE來(lái)完成debug扮宠。比如Pycharm,就可以在代碼中設(shè)置斷點(diǎn),這樣只要程序運(yùn)行到斷點(diǎn)就會(huì)自動(dòng)停止坛增,并可以方便的查看變量的值获雕,當(dāng)然也推薦大家使用這樣的方式進(jìn)行debug。

但是也有一些情況是Pycharm無(wú)法滿足的收捣,比如Pycharm只能針對(duì)本項(xiàng)目中的py代碼進(jìn)行調(diào)試届案,另外很多進(jìn)行數(shù)據(jù)分析、數(shù)據(jù)挖掘和AI算法的代碼其實(shí)大多數(shù)都是在 Jupyter 的 Notebook 中罢艾。那么這種情況就下Pycharm就無(wú)能無(wú)力了楣颠。因此今天我們?yōu)榇蠹曳窒硪粋€(gè)Python中自帶的高效的代碼調(diào)試庫(kù)pdb,那么它是什么咐蚯?又如何使用呢童漩?

使用pdb進(jìn)行代碼調(diào)試

pdb是python自帶的代碼調(diào)試庫(kù),是一個(gè)交互式源代碼調(diào)試器春锋。它支持在源代碼行級(jí)別設(shè)置斷點(diǎn)和單步執(zhí)行矫膨、堆棧幀檢查、源代碼列表以及在任何堆棧幀的上下文中評(píng)估任意Python代碼期奔。

Python官方文檔地址:https://docs.python.org/3/library/pdb.html#module-pdb

如何使用 pdb

要啟動(dòng) pdb 調(diào)試侧馅,我們只需要在程序中,加入import pdbpdb.set_trace()這兩行代碼就行了

a = 1
b = 2
import pdb
pdb.set_trace()
c = 3
print(a + b + c)

接下來(lái)我們運(yùn)行這個(gè)代碼能庆,它的輸出界面是下面這樣的施禾,表示程序已經(jīng)運(yùn)行到了pdb.set_trace()這行,并且暫停了下來(lái)搁胆,等待輸入指令弥搞。

> /Users/yc/Desktop/code/demo.py(5)<module>()
-> c = 3
(Pdb) 

這時(shí),我們就可以執(zhí)行一些調(diào)試渠旁,比如打印變量的語(yǔ)法是p <expression>

(pdb) p a  # 輸入 p a  等于要查看變量a的值
1
(pdb) p b
2
(pdb) p c # 因?yàn)槌绦蚰壳爸贿\(yùn)行了前面幾行攀例,此時(shí)的變量 c 還沒(méi)有被定義:
*** NameError: name 'c' is not defined

如果需要程序繼續(xù)執(zhí)行下一行可以輸入n,表示繼續(xù)執(zhí)行代碼到下一行

(pdb) n
-> print(a + b + c)

查看當(dāng)前代碼行附近的源代碼可以輸入l顾腊,列出當(dāng)前代碼行上下的 11 行源代碼

(pdb) l
  1    a = 1
  2    b = 2
  3    import pdb
  4    pdb.set_trace()
  5  ->  c = 3
  6    print(a + b + c)

pdb常用指令參考

完整指令 簡(jiǎn)寫指令 指令描述
break b <行號(hào)> 在某一行設(shè)置斷點(diǎn)
break b <文件名>:<行號(hào)> 在某個(gè)文件的某行打一點(diǎn)斷點(diǎn)
break b <函數(shù)名> 在某個(gè)含稅的第一行打一個(gè)斷點(diǎn)
clear cl 清除所有斷點(diǎn)
clear cl n1 n2 清除編號(hào)為n1粤铭、n2的斷點(diǎn)
next n 執(zhí)行下一條語(yǔ)句,遇到函數(shù)不進(jìn)入其內(nèi)部
step s 執(zhí)行下一條語(yǔ)句杂靶,遇到函數(shù)會(huì)進(jìn)入其內(nèi)部
continue c或者cont 執(zhí)行到下一個(gè)斷點(diǎn)
list l 列出源碼(前后11行代碼)
list l <行號(hào)> 列出某行周圍11行代碼(即這一行為中間行梆惯,列出它上下各5行)
list l <行號(hào)1> <行號(hào)2> 列出兩個(gè)行號(hào)范圍內(nèi)的代碼
p p 打印變量值,也可以用print
pp pp 好看一點(diǎn)的輸出
quit q 退出 pdb
return r 一直運(yùn)行到函數(shù)返回

以上就是給大家做了一個(gè)簡(jiǎn)單的pdb使用方法吗垮,除了這些常用命令垛吗,還有許多其他的命令可以使用,這里我就不在一一贅述了烁登∏犹耄可以參考對(duì)應(yīng)的官方文檔,來(lái)熟悉這些用法。

用 cProfile 進(jìn)行性能分析

那么除了要對(duì)程序進(jìn)行調(diào)試锨络,性能分析也是每個(gè)開(kāi)發(fā)者的必備技能赌躺。

日常工作中,我們常常會(huì)遇到這樣的問(wèn)題:在線上羡儿,我發(fā)現(xiàn)產(chǎn)品的某個(gè)功能模塊效率低下礼患,延遲(latency)高,占用的資源多掠归,但卻不知道是哪里出了問(wèn)題讶泰。這時(shí),對(duì)代碼進(jìn)行性能分析就顯得非常重要拂到,通過(guò)性能分析你就可以知道程序的瓶頸所在痪署,從而對(duì)其進(jìn)行修正或優(yōu)化。當(dāng)然兄旬,這并不需要你花費(fèi)特別大的力氣狼犯,在 Python 中,這些需求用 cProfile 就可以實(shí)現(xiàn)领铐。

舉個(gè)例子悯森,比如我想實(shí)現(xiàn)一個(gè)斐波拉契數(shù)列,運(yùn)用遞歸思想绪撵,我們很容易就能寫出下面這樣的代碼:

def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)
def fib_seq(n):
    res = []
    if n > 0:
        res.extend(fib_seq(n-1))
    res.append(fib(n))
    return res
fib_seq(30)

接下來(lái)瓢姻,我想要測(cè)試一下這段代碼總的效率以及各個(gè)部分的效率。

那么音诈,我就只需在開(kāi)頭導(dǎo)入 cProfile 這個(gè)模塊幻碱,并且在最后運(yùn)行 cProfile.run() 就可以了:

import cProfile

def fib(n):...
def fib_seq(n):...

cProfile.run('fib_seq(30)')

或者更簡(jiǎn)單一些,直接在運(yùn)行腳本的命令中细溅,加入選項(xiàng)“-m cProfile”也很方便:

python3 -m cProfile xxx.py

運(yùn)行完畢后褥傍,我們可以看到下面這個(gè)輸出界面:


         7049218 function calls (96 primitive calls) in 2.280 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    2.280    2.280 <string>:1(<module>)
     31/1    0.000    0.000    2.280    2.280 demo.py:10(fib_seq)
7049123/31    2.280    0.000    2.280    0.074 demo.py:3(fib)
        1    0.000    0.000    2.280    2.280 {built-in method builtins.exec}
       31    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
       30    0.000    0.000    0.000    0.000 {method 'extend' of 'list' objects}

輸出的第一行

7049218 function calls (96 primitive calls) in 2.280 seconds

是指本次運(yùn)行總計(jì)7049218個(gè)函數(shù)調(diào)用被監(jiān)控,其中96個(gè)是原生調(diào)用(不涉及遞歸)

其它列信息:

  • ncalls喇聊,是指相應(yīng)代碼 / 函數(shù)被調(diào)用的次數(shù)恍风;
  • tottime,是指對(duì)應(yīng)代碼 / 函數(shù)總共執(zhí)行所需要的時(shí)間(注意誓篱,并不包括它調(diào)用的其他代碼 / 函數(shù)的執(zhí)行時(shí)間)朋贬;
  • tottime percall,就是上述兩者相除的結(jié)果窜骄,也就是tottime / ncalls锦募;
  • cumtime,則是指對(duì)應(yīng)代碼 / 函數(shù)總共執(zhí)行所需要的時(shí)間啊研,這里包括了它調(diào)用的其他代碼 / 函數(shù)的執(zhí)行時(shí)
  • cumtime percall御滩,則是 cumtime 和 ncalls 相除的平均結(jié)果。

了解這些參數(shù)后党远,再來(lái)看這個(gè)結(jié)果削解。我們可以清晰地看到,這段程序執(zhí)行效率的瓶頸沟娱,在于第二行的函數(shù) fib()氛驮,它被調(diào)用了 700 多萬(wàn)次。

有沒(méi)有什么辦法可以提高改進(jìn)呢济似?答案是肯定的矫废。通過(guò)觀察,我們發(fā)現(xiàn)砰蠢,程序中有很多對(duì) fib() 的調(diào)用蓖扑,其實(shí)是重復(fù)的,那我們就可以用字典來(lái)保存計(jì)算過(guò)的結(jié)果台舱,防止重復(fù)律杠。

改進(jìn)后的代碼如下所示:

def funcopt(f):
    opt = {}
    def helper(x):
        if x not in opt:            
            opt[x] = f(x)
        return opt[x]
    return helper
@funcopt
def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)
def fib_seq(n):
    res = []
    if n > 0:
        res.extend(fib_seq(n-1))
    res.append(fib(n))
    return res
fib_seq(30)

這時(shí),我們?cè)賹?duì)其進(jìn)行 profile

   216 function calls (128 primitive calls) in 0.000 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 chuange.py:1(<module>)
        1    0.000    0.000    0.000    0.000 chuange.py:1(funcopt)
     31/1    0.000    0.000    0.000    0.000 chuange.py:16(fib_seq)
    89/31    0.000    0.000    0.000    0.000 chuange.py:3(helper)
       31    0.000    0.000    0.000    0.000 chuange.py:8(fib)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.exec}
       31    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
       30    0.000    0.000    0.000    0.000 {method 'extend' of 'list' objects}

此時(shí)你會(huì)看到由原來(lái)的700萬(wàn)次的函數(shù)調(diào)用優(yōu)化到了216次竞惋,性能的提高自然就不用多說(shuō)了柜去。

這個(gè)簡(jiǎn)單的例子,便是 cProfile 的基本用法拆宛,也是我今天分享的重點(diǎn)嗓奢。當(dāng)然,cProfile 還有很多其他功能浑厚,還可以結(jié)合 stats 類來(lái)使用股耽,你可以閱讀相應(yīng)的 官方文檔 來(lái)了解。

總結(jié)

本章節(jié)我們?yōu)榇蠹曳窒砹?Python 中常用的調(diào)試工具 pdb钳幅,和經(jīng)典的性能分析工具 cProfile豺谈。pdb 為 Python 程序提供了一種通用的、交互式的高效率調(diào)試方案贡这;而 cProfile 則是為開(kāi)發(fā)者提供了每個(gè)代碼塊執(zhí)行效率的詳細(xì)分析茬末,有助于我們對(duì)程序的優(yōu)化與提高。關(guān)于它們的更多用法盖矫,你可以通過(guò)它們的官方文檔進(jìn)行實(shí)踐丽惭,熟能生巧。

最后希望各位小伙伴們寫代碼事半功倍辈双,如魚得水责掏。

如果喜歡或者對(duì)你有幫助的小伙伴,歡迎大家關(guān)注我的公眾號(hào):后廠程序員湃望,并分享换衬、點(diǎn)贊痰驱、在看 三連

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瞳浦,隨后出現(xiàn)的幾起案子担映,更是在濱河造成了極大的恐慌,老刑警劉巖叫潦,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝇完,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡矗蕊,警方通過(guò)查閱死者的電腦和手機(jī)短蜕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)傻咖,“玉大人朋魔,你說(shuō)我怎么就攤上這事∏洳伲” “怎么了铺厨?”我有些...
    開(kāi)封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)硬纤。 經(jīng)常有香客問(wèn)我解滓,道長(zhǎng),這世上最難降的妖魔是什么筝家? 我笑而不...
    開(kāi)封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任洼裤,我火速辦了婚禮,結(jié)果婚禮上溪王,老公的妹妹穿的比我還像新娘腮鞍。我一直安慰自己,他們只是感情好莹菱,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布移国。 她就那樣靜靜地躺著,像睡著了一般道伟。 火紅的嫁衣襯著肌膚如雪迹缀。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天蜜徽,我揣著相機(jī)與錄音祝懂,去河邊找鬼。 笑死拘鞋,一個(gè)胖子當(dāng)著我的面吹牛砚蓬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播盆色,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼灰蛙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼祟剔!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起摩梧,我...
    開(kāi)封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤物延,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后障本,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡响鹃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年驾霜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片买置。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡粪糙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出忿项,到底是詐尸還是另有隱情蓉冈,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布轩触,位于F島的核電站寞酿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏脱柱。R本人自食惡果不足惜伐弹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望榨为。 院中可真熱鬧惨好,春花似錦、人聲如沸随闺。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)矩乐。三九已至龄句,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間散罕,已是汗流浹背撒璧。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留笨使,地道東北人卿樱。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像硫椰,于是被迫代替她去往敵國(guó)和親繁调。 傳聞我的和親對(duì)象是個(gè)殘疾皇子萨蚕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348