这是什么cProfile结果告诉我,我需要解决?
-
29-09-2019 - |
题
我想改善性能的Python脚本,并已经使用 cProfile
产生一个执行情况报告:
python -m cProfile -o chrX.prof ./bgchr.py ...args...
我打开这个 chrX.prof
文件蟒蛇的 pstats
并且打印出来的统计数据:
Python 2.7 (r27:82500, Oct 5 2010, 00:24:22)
[GCC 4.1.2 20080704 (Red Hat 4.1.2-44)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pstats
>>> p = pstats.Stats('chrX.prof')
>>> p.sort_stats('name')
>>> p.print_stats()
Sun Oct 10 00:37:30 2010 chrX.prof
8760583 function calls in 13.780 CPU seconds
Ordered by: function name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 {_locale.setlocale}
1 1.128 1.128 1.128 1.128 {bz2.decompress}
1 0.002 0.002 13.780 13.780 {execfile}
1750678 0.300 0.000 0.300 0.000 {len}
48 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}
1 0.000 0.000 0.000 0.000 {method 'close' of 'file' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1750676 0.496 0.000 0.496 0.000 {method 'join' of 'str' objects}
1 0.007 0.007 0.007 0.007 {method 'read' of 'file' objects}
1 0.000 0.000 0.000 0.000 {method 'readlines' of 'file' objects}
1 0.034 0.034 0.034 0.034 {method 'rstrip' of 'str' objects}
23 0.000 0.000 0.000 0.000 {method 'seek' of 'file' objects}
1757785 1.230 0.000 1.230 0.000 {method 'split' of 'str' objects}
1 0.000 0.000 0.000 0.000 {method 'startswith' of 'str' objects}
1750676 0.872 0.000 0.872 0.000 {method 'write' of 'file' objects}
1 0.007 0.007 13.778 13.778 ./bgchr:3(<module>)
1 0.000 0.000 13.780 13.780 <string>:1(<module>)
1 0.001 0.001 0.001 0.001 {open}
1 0.000 0.000 0.000 0.000 {sys.exit}
1 0.000 0.000 0.000 0.000 ./bgchr:36(checkCommandLineInputs)
1 0.000 0.000 0.000 0.000 ./bgchr:27(checkInstallation)
1 1.131 1.131 13.701 13.701 ./bgchr:97(extractData)
1 0.003 0.003 0.007 0.007 ./bgchr:55(extractMetadata)
1 0.064 0.064 13.771 13.771 ./bgchr:5(main)
1750677 8.504 0.000 11.196 0.000 ./bgchr:122(parseJarchLine)
1 0.000 0.000 0.000 0.000 ./bgchr:72(parseMetadata)
1 0.000 0.000 0.000 0.000 /home/areynolds/proj/tools/lib/python2.7/locale.py:517(setlocale)
问题:我能做些什么 join
, split
和 write
操作,以减少显而易见的影响他们有关的性能,这个剧本吗?
如果它是相关的,这里是完整的源代码的脚本中的问题:
#!/usr/bin/env python
import sys, os, time, bz2, locale
def main(*args):
# Constants
global metadataRequiredFileSize
metadataRequiredFileSize = 8192
requiredVersion = (2,5)
# Prep
global whichChromosome
whichChromosome = "all"
checkInstallation(requiredVersion)
checkCommandLineInputs()
extractMetadata()
parseMetadata()
if whichChromosome == "--list":
listMetadata()
sys.exit(0)
# Extract
extractData()
return 0
def checkInstallation(rv):
currentVersion = sys.version_info
if currentVersion[0] == rv[0] and currentVersion[1] >= rv[1]:
pass
else:
sys.stderr.write( "\n\t[%s] - Error: Your Python interpreter must be %d.%d or greater (within major version %d)\n" % (sys.argv[0], rv[0], rv[1], rv[0]) )
sys.exit(-1)
return
def checkCommandLineInputs():
cmdName = sys.argv[0]
argvLength = len(sys.argv[1:])
if (argvLength == 0) or (argvLength > 2):
sys.stderr.write( "\n\t[%s] - Usage: %s [<chromosome> | --list] <bjarch-file>\n\n" % (cmdName, cmdName) )
sys.exit(-1)
else:
global inFile
global whichChromosome
if argvLength == 1:
inFile = sys.argv[1]
elif argvLength == 2:
whichChromosome = sys.argv[1]
inFile = sys.argv[2]
if inFile == "-" or inFile == "--list":
sys.stderr.write( "\n\t[%s] - Usage: %s [<chromosome> | --list] <bjarch-file>\n\n" % (cmdName, cmdName) )
sys.exit(-1)
return
def extractMetadata():
global metadataList
global dataHandle
metadataList = []
dataHandle = open(inFile, 'rb')
try:
for data in dataHandle.readlines(metadataRequiredFileSize):
metadataLine = data
metadataLines = metadataLine.split('\n')
for line in metadataLines:
if line:
metadataList.append(line)
except IOError:
sys.stderr.write( "\n\t[%s] - Error: Could not extract metadata from %s\n\n" % (sys.argv[0], inFile) )
sys.exit(-1)
return
def parseMetadata():
global metadataList
global metadata
metadata = []
if not metadataList: # equivalent to "if len(metadataList) > 0"
sys.stderr.write( "\n\t[%s] - Error: No metadata in %s\n\n" % (sys.argv[0], inFile) )
sys.exit(-1)
for entryText in metadataList:
if entryText: # equivalent to "if len(entryText) > 0"
entry = entryText.split('\t')
filename = entry[0]
chromosome = entry[0].split('.')[0]
size = entry[1]
entryDict = { 'chromosome':chromosome, 'filename':filename, 'size':size }
metadata.append(entryDict)
return
def listMetadata():
for index in metadata:
chromosome = index['chromosome']
filename = index['filename']
size = long(index['size'])
sys.stdout.write( "%s\t%s\t%ld" % (chromosome, filename, size) )
return
def extractData():
global dataHandle
global pLength
global lastEnd
locale.setlocale(locale.LC_ALL, 'POSIX')
dataHandle.seek(metadataRequiredFileSize, 0) # move cursor past metadata
for index in metadata:
chromosome = index['chromosome']
size = long(index['size'])
pLength = 0L
lastEnd = ""
if whichChromosome == "all" or whichChromosome == index['chromosome']:
dataStream = dataHandle.read(size)
uncompressedData = bz2.decompress(dataStream)
lines = uncompressedData.rstrip().split('\n')
for line in lines:
parseJarchLine(chromosome, line)
if whichChromosome == chromosome:
break
else:
dataHandle.seek(size, 1) # move cursor past chromosome chunk
dataHandle.close()
return
def parseJarchLine(chromosome, line):
global pLength
global lastEnd
elements = line.split('\t')
if len(elements) > 1:
if lastEnd:
start = long(lastEnd) + long(elements[0])
lastEnd = long(start + pLength)
sys.stdout.write("%s\t%ld\t%ld\t%s\n" % (chromosome, start, lastEnd, '\t'.join(elements[1:])))
else:
lastEnd = long(elements[0]) + long(pLength)
sys.stdout.write("%s\t%ld\t%ld\t%s\n" % (chromosome, long(elements[0]), lastEnd, '\t'.join(elements[1:])))
else:
if elements[0].startswith('p'):
pLength = long(elements[0][1:])
else:
start = long(long(lastEnd) + long(elements[0]))
lastEnd = long(start + pLength)
sys.stdout.write("%s\t%ld\t%ld\n" % (chromosome, start, lastEnd))
return
if __name__ == '__main__':
sys.exit(main(*sys.argv))
编辑
如果我的评论了 sys.stdout.write
发言的第一个条件的 parseJarchLine()
, 然后我的运行时间的推移,从10.2秒4.8秒:
# with first conditional's "sys.stdout.write" enabled
$ time ./bgchr chrX test.bjarch > /dev/null
real 0m10.186s
user 0m9.917s
sys 0m0.160s
# after first conditional's "sys.stdout.write" is commented out
$ time ./bgchr chrX test.bjarch > /dev/null
real 0m4.808s
user 0m4.561s
sys 0m0.156s
是写到 stdout
真的那昂贵蟒蛇?
解决方案
ncalls
仅在将数字与其他计数(例如文件中的字符/字段/行数)进行比较的程度上相关。 tottime
和 cumtime
真正重要的是。 cumtime
是在函数/方法上花费的时间 包含 在其调用的函数/方法上花费的时间; tottime
是在函数/方法上花费的时间 排除 在其调用的函数/方法上花费的时间。
我发现对统计数据进行分类很有帮助 tottime
再继续 cumtime
, , 不开 name
.
bgchar
确实 指执行脚本,并不是无关紧要的,因为它在13.5中占8.9秒; 8.9秒没有在其调用的函数/方法中包括时间!仔细阅读@lie Ryan关于将脚本置于功能的言论,并实施他的建议。同样,@Jonesy所说的。
string
被提及,因为你 import string
并仅在一个地方使用它: string.find(elements[0], 'p')
. 。在输出中的另一行上,您会注意到字符串。输入一次仅一次,因此在此脚本的此运行中,这不是性能问题。但是:您使用 str
其他地方的方法。 string
如今已弃用功能,并通过调用相应的功能来实现 str
方法。你会更好地写作 elements[0].find('p') == 0
确切但更快地等效,并且可能喜欢使用 elements[0].startswith('p')
这会节省读者,想知道这是否 == 0
实际上应该是 == -1
.
@Bernd Petersohn提到的四种方法仅在13.541秒的总执行时间中仅占用3.7秒。在担心这些问题之前,将您的脚本模块化为函数,再次运行CPROFILE,然后通过 tottime
.
使用更改脚本修订的问题后更新:
“”问题:关于加入,分裂和写操作我该怎么办,以减少它们对该脚本的表现的明显影响?”
嗯?这三个在一起的总计为13.8的2.6秒。您的parsejarchline功能需要8.5秒(其中不包括由其调用的函数/方法所花费的时间。 assert(8.5 > 2.6)
伯恩(Bernd)已经指出了您可能考虑使用的事情。您毫不犹豫地完全将线路分开,只是在编写它时再次加入。您只需要检查第一个元素。代替 elements = line.split('\t')
做 elements = line.split('\t', 1)
并更换 '\t'.join(elements[1:])
经过 elements[1]
.
现在让我们深入研究Parsejarchline的身体。用途和用途的用途数量 long
内置功能令人惊讶。同样令人惊讶的是一个事实 long
在Cprofile输出中未提及。
为什么你需要 long
根本吗?文件超过2 GB?好的,然后您需要考虑这一点,因为Python 2.2, int
溢出导致促销 long
而不是提出例外。您可以利用更快的执行 int
算术。您还需要考虑这样做 long(x)
什么时候 x
已经明显了 long
是浪费资源。
这是标记为[1]的删除垃圾更改的parsejarchline函数,标记为[2]。好主意:以小步骤进行更改,重新测试,重新审核。
def parseJarchLine(chromosome, line):
global pLength
global lastEnd
elements = line.split('\t')
if len(elements) > 1:
if lastEnd != "":
start = long(lastEnd) + long(elements[0])
# [1] start = lastEnd + long(elements[0])
# [2] start = lastEnd + int(elements[0])
lastEnd = long(start + pLength)
# [1] lastEnd = start + pLength
sys.stdout.write("%s\t%ld\t%ld\t%s\n" % (chromosome, start, lastEnd, '\t'.join(elements[1:])))
else:
lastEnd = long(elements[0]) + long(pLength)
# [1] lastEnd = long(elements[0]) + pLength
# [2] lastEnd = int(elements[0]) + pLength
sys.stdout.write("%s\t%ld\t%ld\t%s\n" % (chromosome, long(elements[0]), lastEnd, '\t'.join(elements[1:])))
else:
if elements[0].startswith('p'):
pLength = long(elements[0][1:])
# [2] pLength = int(elements[0][1:])
else:
start = long(long(lastEnd) + long(elements[0]))
# [1] start = lastEnd + long(elements[0])
# [2] start = lastEnd + int(elements[0])
lastEnd = long(start + pLength)
# [1] lastEnd = start + pLength
sys.stdout.write("%s\t%ld\t%ld\n" % (chromosome, start, lastEnd))
return
更新一个问题 sys.stdout.write
如果您发表评论的陈述类似于原始的陈述:
sys.stdout.write("%s\t%ld\t%ld\t%s\n" % (chromosome, start, lastEnd, '\t'.join(elements[1:])))
然后您的问题很有趣。试试这个:
payload = "%s\t%ld\t%ld\t%s\n" % (chromosome, start, lastEnd, '\t'.join(elements[1:]))
sys.stdout.write(payload)
现在 评论 sys.stdout.write
陈述 ...
顺便说一句,有人在评论中提到有人将其分为多个写作...您是否考虑过?元素平均有多少个字节[1:]?在染色体中?
===主题的更改:让我担心您初始化 lastEnd
到 ""
而不是零,没有人对此发表评论。无论如何,您都应该解决此问题,这允许简化相当大的简化,并添加他人的建议:
def parseJarchLine(chromosome, line):
global pLength
global lastEnd
elements = line.split('\t', 1)
if elements[0][0] == 'p':
pLength = int(elements[0][1:])
return
start = lastEnd + int(elements[0])
lastEnd = start + pLength
sys.stdout.write("%s\t%ld\t%ld" % (chromosome, start, lastEnd))
if elements[1:]:
sys.stdout.write(elements[1])
sys.stdout.write(\n)
现在,我同样担心两个全球变量 lastEnd
和 pLength
- parsejarchline函数现在很小,以至于可以折回其唯一呼叫者的主体, extractData
, ,它节省了两个全局变量和一个数十亿个功能调用。您还可以节省大量的查找 sys.stdout.write
通过放置 write = sys.stdout.write
一旦到达前面 extractData
然后使用它。
顺便说一句,Python 2.5或更高的脚本测试;您是否尝试在2.5和2.6上进行分析?
其他提示
如Lie Ryan所述,如果您的代码更模块化,则此输出将更有用。但是,您可以从输出中获取几件事,只是查看源代码:
您正在进行很多比较,而Python实际上并不需要。例如,而不是:
if len(entryText) > 0:
您可以写:
if entryText:
一个空列表在Python中评估为False。对于一个空字符串也是如此,您还可以在代码中测试该字符串,并且更改它也将使代码更短,更可读性,因此不要以此为代替:
for line in metadataLines:
if line == '':
break
else:
metadataList.append(line)
您可以做:
for line in metadataLines:
if line:
metadataList.append(line)
关于组织和绩效,此代码还有其他几个问题。您可以多次为同一事物分配变量,而不仅仅是创建一个对象实例并在对象上进行所有访问。这样做将减少任务数量以及全局变量的数量。我不想听起来过于批评,但是此代码似乎并没有记住性能。
该项目有关的可能的优化那些具有高值 ncalls 和 tottime. bgchr:4(<module>)
和 <string>:1(<module>)
可能提到的执行模块的身体并不是有关在这里。
很明显,你的表现问题来自串的处理。这也许应减少。热点 split
, join
和 sys.stdout.write
. bz2.decompress
似乎也是昂贵的。
我建议你尝试如下:
- 你的主要数据似乎包括符分隔CSV值。尝试,如果CSV读者进行更好。
- sys.stdout是线和缓冲刷新,每次新行编写的。考虑编写一个文件与较大的缓冲区的大小。
- 而不是加入元素之前,将它们写出来,写入他们顺序输出文件。你也可以考虑采用CSV作家。
- 而不是解数据,一旦进入一个字符串中使用一个BZ2File对象,并通过到CSV读者。
它似乎是循环体的实际解压缩的数据仅仅是援引一次。也许你找到一种方法避免的话 dataHandle.read(size)
, ,其产生的巨大串,然后解压缩,并与该文件的对象。
增编: BZ2File可能不适用于你的情况,因为它要求一名参数。你需要什么东西就像一个文件对象的图与综合读的限制,相当于ZipExtFile但使用BZ2Decompressor为解压。
我的主要观点在这里这是你的代码应改为执行更迭代处理的数据,而不是吃它作为一个整体和分裂它再次之后。