このcrofileの結果は、私が修正する必要があると言っていますか?
-
29-09-2019 - |
質問
Pythonスクリプトのパフォーマンスを改善したいと思います cProfile
パフォーマンスレポートを生成するには:
python -m cProfile -o chrX.prof ./bgchr.py ...args...
私はこれを開きました chrX.prof
Pythonのファイル 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
Pythonでは本当に高価ですか?
解決
ncalls
ファイル内のchars/fields/行の数などの他のカウントと数値を比較すると、蛍光度が高い場合があります。 tottime
と cumtime
本当に重要なことです。 cumtime
関数/方法で費やされる時間です 含む 呼び出す関数/方法で費やされた時間。 tottime
関数/方法で費やされる時間です 除外 呼び出す関数/方法で費やされた時間。
統計を並べ替えると便利です tottime
そして再び cumtime
, 、オンではありません name
.
bgchar
絶対に スクリプトの実行を指し、13.5から8.9秒かかるため、無関係ではありません。その8.9秒には、呼び出す関数/方法に時間が含まれていません! @lie Ryanがスクリプトを機能にモジュール化することについて言っていることを注意深く読んで、彼のアドバイスを実装してください。同様に、 @Jonesyが言うこと。
string
あなたのために言及されています import string
そして、それを1つだけで使用します: string.find(elements[0], 'p')
. 。出力の別の行では、string.findが1回だけ呼ばれたことに気付くので、このスクリプトの実行ではパフォーマンスの問題ではありません。ただし、使用します str
他のどこでも方法。 string
関数は最近廃止されており、対応するものを呼び出すことによって実装されています str
方法。あなたはもっと書く方が良いでしょう elements[0].find('p') == 0
正確ではあるがより速い同等で、使用したいかもしれません elements[0].startswith('p')
それはそれを疑問に思う読者を救うでしょう == 0
実際にあるべきです == -1
.
@bernd Petersohnが言及した4つの方法は、13.541秒の合計実行時間のうち3.7秒しかかかりません。それらについてあまりにも心配する前に、スクリプトを関数にモジュール化し、もう一度cprofileを実行し、統計を並べ替えます tottime
.
変更されたスクリプトで修正された質問の後に更新:
"" "質問:参加、分割、書き込み操作について何ができますか?
は?これら3は、合計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]
.
それでは、パーセジャルラインの体に飛び込みましょう。ソースの用途と使用の数 long
組み込み機能は驚くべきものです。また、驚くべきことです long
CProfile出力では言及されていません。
なぜ必要なのですか long
まったく? 2 GBを超えるファイル?さて、Python 2.2からそれを考慮する必要があります int
オーバーフローはプロモーションを引き起こします long
例外を提起する代わりに。より速い実行を利用できます int
算術。また、それをすることを考慮する必要があります long(x)
いつ x
すでに明らかにaです long
リソースの無駄です。
以下は、マークされた廃棄物の変更を削除し、nit-to-intに変化する[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)
今、私は同様に2つのグローバル変数について心配しています lastEnd
と pLength
- パースジャルライン関数は非常に小さいため、唯一の発信者の本体に戻すことができます。 extractData
, 、2つのグローバル変数と数億個の関数呼び出しを保存します。また、数億個のルックアップを保存することもできます sys.stdout.write
置くことによって write = sys.stdout.write
前に上がると extractData
代わりにそれを使用します。
ところで、Python 2.5以上のスクリプトテスト。 2.5と2.6でプロファイリングを試しましたか?
他のヒント
ライアンが述べたように、コードがよりモジュール化されている場合、この出力はより便利になります。ただし、出力からピックアップしてソースコードを見ることができるいくつかのこと:
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)
組織とパフォーマンスの両方に関して、このコードには他にもいくつかの問題があります。たとえば、オブジェクトインスタンスを1回だけ作成し、オブジェクト上のすべてのアクセスを実行する代わりに、変数を同じものに複数回割り当てます。これを行うと、割り当ての数とグローバル変数の数が減ります。私は過度に重要に聞こえたくありませんが、このコードはパフォーマンスを念頭に置いて書かれているようには見えません。
可能な最適化に関連するエントリは、 ncalls と トッティム. 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を使用します。
ここでの私の主なポイントは、データ全体を丸鳴らしてその後再び分割するのではなく、データのより反復処理を実行するためにコードを変更する必要があることです。