千锋教育-做有情怀、有良心、有品质的职业教育机构

手机站
千锋教育

千锋学习站 | 随时随地免费学

千锋教育

扫一扫进入千锋手机站

领取全套视频
千锋教育

关注千锋学习站小程序
随时随地免费学习课程

当前位置:首页  >  技术干货  > Python性能分析

Python性能分析

来源:千锋教育
发布人:xqq
时间: 2023-11-07 19:23:11 1699356191

python标准库提供两个代码性能分析相关的模块,即timeit和cProfile/profile。前者更适合测试简短的代码片段,后者则可分析代码片段乃至整体模块中各个函数的调用次数、运行耗时等信息。

cProfile是profile的C版本,开销更小。基于cProfile模块,可方便地评估程序性能瓶颈(bottleneck),借以发现程序中值得优化的短板。

根据粒度不同,可将cProfile使用场景分为三类。

1.1分析单条语句

importcProfile,pstats,re,cStringIO

cProfile.run('re.compile("foo|bar")','prfRes')#将cProfile的结果写入prfRes文件

p=pstats.Stats('prfRes')#pstats读取cProfile输出结果

#strip_dirs()剥除模块名的无关路径(如C:\Python27\lib\)

#sort_stats('cumtime')或sort_stats('cumulative')按照cumtime对打印项排序

#print_stats(n)打印输出前10行统计项(不指定n则打印所有项)

p.strip_dirs().sort_stats('cumtime').print_stats(5)

pstats模块可用多种方式对cProfile性能分析结果进行排序并输出。运行结果如下:

TueMay2413:56:072016prfRes

195functioncalls(190primitivecalls)in0.001seconds

Orderedby:cumulativetime

Listreducedfrom33to5duetorestriction<5>

ncallstottimepercallcumtimepercallfilename:lineno(function)

10.0000.0000.0010.001:1()

10.0000.0000.0010.001re.py:192(compile)

10.0000.0000.0010.001re.py:230(_compile)

10.0000.0000.0010.001sre_compile.py:567(compile)

10.0000.0000.0000.000sre_compile.py:552(_code)

其中,tottime表示某函数的总计运行时间(不含该函数内调用的子函数运行时间),cumtime表示某函数及其调用的子函数的累积运行时间。

1.2分析代码片段

pr=cProfile.Profile()

pr.enable()#以下为待分析代码段

regMatch=re.match('^([^/]*)/(/|\*)+(.*)$','//*suspicious')

printregMatch.groups()

pr.disable()#以上为待分析代码段

s=cStringIO.StringIO()

pstats.Stats(pr,stream=s).sort_stats('cumulative').print_stats(10)

prints.getvalue()

运行结果如下:

('','*','suspicious')

536functioncalls(512primitivecalls)in0.011seconds

Orderedby:cumulativetime

Listreducedfrom78to10duetorestriction<10>

ncallstottimepercallcumtimepercallfilename:lineno(function)

20.0000.0000.0090.005C:\Python27\lib\idlelib\PyShell.py:1343(write)

20.0000.0000.0090.005C:\Python27\lib\idlelib\rpc.py:591(__call__)

20.0000.0000.0090.005C:\Python27\lib\idlelib\rpc.py:208(remotecall)

20.0000.0000.0090.004C:\Python27\lib\idlelib\rpc.py:238(asyncreturn)

20.0000.0000.0090.004C:\Python27\lib\idlelib\rpc.py:279(getresponse)

20.0000.0000.0090.004C:\Python27\lib\idlelib\rpc.py:295(_getresponse)

20.0000.0000.0090.004C:\Python27\lib\threading.py:309(wait)

80.0090.0010.0090.001{method'acquire'of'thread.lock'objects}

10.0000.0000.0020.002C:\Python27\lib\re.py:138(match)

10.0000.0000.0020.002C:\Python27\lib\re.py:230(_compile)

1.3分析整个模块

使用命令行,调用cProfile脚本分析其他脚本文件。命令格式为:

python-mcProfile[-ooutput_file][-ssort_order]myscript.py

注意,-o和-s选项不可同时使用。

以C代码统计工具为例,运行如下命令:

E:\PyTest>python-mcProfile-stottimeCLineCounter.pysource-d-b>out.txt

截取out.txt文件部分内容如下:

250316245433620.25xtm_mgr.c

1408729374932093169380.26

762068functioncalls(762004primitivecalls)in2.967seconds

Orderedby:internaltime

ncallstottimepercallcumtimepercallfilename:lineno(function)

820.9850.0122.8690.035CLineCounter.py:11(CalcLines)

1176400.6120.0001.3150.000re.py:138(match)

1176500.3810.0000.3810.000{method'match'of'_sre.SRE_Pattern'objects}

1176550.3190.0000.3240.000re.py:230(_compile)

1380500.1980.0000.1980.000{method'isspace'of'str'objects}

1058230.1650.0000.1650.000{method'strip'of'str'objects}

123156/1231410.1540.0000.1540.000{len}

378870.0550.0000.0550.000{method'group'of'_sre.SRE_Match'objects}

820.0410.0000.0410.000{method'readlines'of'file'objects}

820.0160.0000.0160.000{open}

10.0040.0042.9502.950CLineCounter.py:154(CountDir)

由tottime可见,此处的性能瓶颈在于CalcLines()函数和其中的正则表达式处理。而isspace()和strip()方法及len()函数因调用次数较多,总的耗时也颇为可观。

作为对比,以下给出一种未使用正则表达式的统计实现:

defCalcLines(line,isBlockComment):

lineType,lineLen=0,len(line)

line=line+'\n'#添加一个字符防止iChar+1时越界

iChar,isLineComment=0,False

whileiChar

#行结束符(Windows:\r\n;Mac:\r;Unix:\n)

ifline[iChar]=='\r'orline[iChar]=='\n':

break

elifline[iChar]==''orline[iChar]=='\t':#空白字符

iChar+=1;continue

elifline[iChar]=='/'andline[iChar+1]=='/':#行注释

isLineComment=True

lineType|=2;iChar+=1#跳过'/'

elifline[iChar]=='/'andline[iChar+1]=='*':#块注释开始符

isBlockComment[0]=True

lineType|=2;iChar+=1

elifline[iChar]=='*'andline[iChar+1]=='/':#块注释结束符

isBlockComment[0]=False

lineType|=2;iChar+=1

else:

ifisLineCommentorisBlockComment[0]:

lineType|=2

else:

lineType|=1

iChar+=1

returnlineType#Bitmap:0空行,1代码,2注释,3代码和注释

在CalcLines()函数中。参数line为当前文件行字符串,参数isBlockComment指示当前行是否位于块注释内。该函数直接分析句法,而非模式匹配。注意,行结束符可能因操作系统而异,因此应区分CR(回车)和LF(换行)符。此外,也可在读取文件时采用"rU"(即通用换行模式),该模式会将行结束符\r\n和\r替换为\n。

基于新的CalcLines()函数,CountFileLines()函数需作如下修改:

defCountFileLines(filePath,isRawReport=True,isShortName=False):

fileExt=os.path.splitext(filePath)

iffileExt[1]!='.c'andfileExt[1]!='.h':

return

isBlockComment=[False]#或定义为全局变量,以保存上次值

lineCountInfo=[0]*4#[代码总行数,代码行数,注释行数,空白行数]

withopen(filePath,'r')asfile:

forlineinfile:

lineType=CalcLines(line,isBlockComment)

lineCountInfo[0]+=1

iflineType==0:lineCountInfo[3]+=1

eliflineType==1:lineCountInfo[1]+=1

eliflineType==2:lineCountInfo[2]+=1

eliflineType==3:lineCountInfo[1]+=1;lineCountInfo[2]+=1

else:

assertFalse,'UnexpectedlineType:%d(0~3)!'%lineType

ifisRawReport:

globalrawCountInfo

rawCountInfo[:-1]=[x+yforx,yinzip(rawCountInfo[:-1],lineCountInfo)]

rawCountInfo[-1]+=1

elifisShortName:

detailCountInfo.append([os.path.basename(filePath),lineCountInfo])

else:

detailCountInfo.append([filePath,lineCountInfo])

将这种统计实现命名为BCLineCounter.py。通过cProfile命令分析其性能,截取out.txt文件部分内容如下:

250316245433620.25xtm_mgr.c

1408729373632106169380.26

286013functioncalls(285979primitivecalls)in3.926seconds

Orderedby:internaltime

ncallstottimepercallcumtimepercallfilename:lineno(function)

1408723.3340.0003.4750.000BCLineCounter.py:15(CalcLines)

830.4090.0053.9030.047BCLineCounter.py:45(CountFileLines)

141593/1415850.1420.0000.1420.000{len}

820.0140.0000.0140.000{open}

10.0040.0040.0040.004collections.py:1()

4160.0030.0000.0040.000ntpath.py:96(splitdrive)

840.0020.0000.0020.000{nt._isdir}

10.0020.0020.0070.007argparse.py:62()

10.0020.0023.9263.926BCLineCounter.py:6()

可见,性能并不如CLineCounter.py。因此,使用标准库(如re)提供的函数或方法,不失为明智的选择。

此外,对比BCLineCounter.py和CLineCounter.py的详细行数报告可知,两者的统计结果存在细微差异(正负误差不超过5行)。差异主要体现在有效代码行和纯注释行统计上,因为总行数和空白行数通常不会出现统计误差。那么,哪种实现更可靠呢?

作者首先想到挑选存在统计差异的文件,人工或半人工地删除纯注释行和空白行,从而得到精确的有效代码行数。之所以不编写脚本自动删除上述类型的文件行,是因为作者对于注释行的解析已经存在误差,无法作为基准参考。

C语言预处理器可剔除代码注释,但同时也会剔除#if0...#endif之类的无效语句,不满足要求。于是,作者用UEStudio打开源文件,进入【搜索(Search)】|【替换(Replace)】页,选择Unix正则表达式引擎,用^\s*/\*.*\*/匹配单行注释(/*abc*/)并替换为空字符,用^\s*//.*$匹配单行注释(//abc)并替换为空字符。然后,查找并手工删除跨行注释及其他未匹配到的单行注释。最后,选择UltraEdit正则表达式引擎,用%[^t]++^p匹配空行并替换为空字符,即可删除所有空行。注意,UEStudio帮助中提供的正则表达式^p$一次只能删除一个空行。

按上述方式处理两个大型文件后,初步发现BCLineCounter.py关于有效代码行数的统计是正确的。然而,这种半人工处理方式太过低效,因此作者想到让两个脚本处理相同的文件,并输出有效代码行或纯注释行的内容,将其通过AraxisMerge对比。该工具会高亮差异行,且人工检查很容易鉴别正误。此处,作者假定对于给定文件的给定类型行数,BCLineCounter.py和CLineCounter.py必有一者统计正确(可作基准)。当然,也有可能两者均有误差。因此,若求保险,也可同时输出类型和行内容,再行对比。

综合检查结果发现,BCLineCounter.py较CLineCounter.py更为健壮。这是因为,模式匹配需要处理的场景繁多,极易疏漏。例如,CLineCounter.py无法正确处理下面的代码片段:

voidtest(){

/*/multiline,

comment*/

inta=1/2;//comment

//*Assignavalue

}

读者若有兴趣,可修改和调试CLineCounter.py里的正则表达式,使该脚本高效而健壮。

以上内容为大家介绍了Python性能分析,希望对大家有所帮助,如果想要了解更多Python相关知识,请关注IT培训机构:千锋教育。

tags: python培训
声明:本站稿件版权均属千锋教育所有,未经许可不得擅自转载。
10年以上业内强师集结,手把手带你蜕变精英
请您保持通讯畅通,专属学习老师24小时内将与您1V1沟通
免费领取
今日已有369人领取成功
刘同学 138****2860 刚刚成功领取
王同学 131****2015 刚刚成功领取
张同学 133****4652 刚刚成功领取
李同学 135****8607 刚刚成功领取
杨同学 132****5667 刚刚成功领取
岳同学 134****6652 刚刚成功领取
梁同学 157****2950 刚刚成功领取
刘同学 189****1015 刚刚成功领取
张同学 155****4678 刚刚成功领取
邹同学 139****2907 刚刚成功领取
董同学 138****2867 刚刚成功领取
周同学 136****3602 刚刚成功领取
相关推荐HOT