15

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) 

質問:このスクリプトのパフォーマンスに与える明らかな影響を減らすためにjoinsplitおよび操作について何ができますか?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

Pythonでの書き込みはstdout本当にそれほど高価ですか?

4

3 に答える 3

29

ncallsファイル内の文字/フィールド/行の数などの他のカウントと数値を比較すると、異常が発生する可能性がある場合にのみ関連します。tottimeそしてそれcumtimeが本当に重要なことです。呼び出す関数/メソッドに費やされた時間を含むcumtime、関数/メソッドに費やされた時間です。呼び出される関数/メソッドに費やされた時間を除いた、関数/メソッドに費やされた時間です。tottime

ではなく、で統計tottimeを並べ替えると便利です。cumtimename

bgchar 間違いなくスクリプトの実行を指し、13.5から8.9秒かかるため、無関係ではありません。その8.9秒には、呼び出す関数/メソッドの時間が含まれていません。スクリプトを関数にモジュール化することについて@LieRyanが言っていることを注意深く読み、彼のアドバイスを実装してください。同様に@jonesyが言うこと。

stringimport 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')doの代わりに。elements = line.split('\t', 1)に置き換えます。'\t'.join(elements[1:])elements[1]

それでは、parseJarchLineの本体について詳しく見ていきましょう。ソースでの使用数とlong組み込み関数の使用方法は驚くべきものです。longまた、cProfileの出力に記載されていないという事実も驚くべきことです。

なぜあなたlongはまったく必要なのですか?2 Gbを超えるファイル?intOK、Python 2.2以降、オーバーフローによってlong例外が発生する代わりに昇格が発生することを考慮する必要があります。算術演算の高速実行を利用できますint。また、すでに明らかになっているlong(x)ときに実行することは、リソースの浪費であることを考慮する必要があります。xlong

これは、[1]とマークされた廃棄物の変更と[2]とマークされたintへの変更の変更を含むparseJarchLine関数です。良いアイデア:小さなステップで変更を加え、再テストし、再プロファイルします。

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次に、ステートメントをコメントアウトします...

ちなみに、これを複数の書き込みに分割することについてのコメントで誰かが言及しました...あなたはこれを考慮しましたか?elements [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。parseJarchLine関数は非常に小さいため、2つのグローバル変数を保存pLengthする唯一の呼び出し元の本体に折りたたむことができ、膨大な数の関数が呼び出されます。一度前に置いて代わりにそれを使用することextractDataで、膨大な数のルックアップを節約することもできます。sys.stdout.writewrite = sys.stdout.writeextractData

ところで、スクリプトはPython2.5以降をテストします。2.5と2.6でプロファイリングを試しましたか?

于 2010-10-10T04:13:46.620 に答える
2

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)

このコードには、編成とパフォーマンスの両方の点で他にもいくつかの問題があります。たとえば、オブジェクトインスタンスを一度作成してオブジェクトに対してすべてのアクセスを行うのではなく、同じものに変数を複数回割り当てます。これを行うと、割り当ての数が減り、グローバル変数の数も減ります。過度に批判的に聞こえたくないのですが、このコードはパフォーマンスを念頭に置いて書かれているようには見えません。

于 2010-10-10T02:21:23.613 に答える
-1

可能な最適化に関連するエントリは、ncallstottimeの値が高いエントリです。おそらくモジュール本体の実行を参照しており、ここでは関係ありませんbgchr:4(<module>)<string>:1(<module>)

明らかに、パフォーマンスの問題は文字列処理に起因します。これはおそらく減らす必要があります。ホットスポットは、、splitです。また、費用がかかるようです。joinsys.stdout.writebz2.decompress

次のことを試してみることをお勧めします。

  • メインデータは、タブで区切られたCSV値で構成されているようです。CSVリーダーのパフォーマンスが優れている場合は、試してみてください。
  • sys.stdoutは、改行が書き込まれるたびに行バッファリングおよびフラッシュされます。より大きなバッファサイズのファイルへの書き込みを検討してください。
  • 要素を書き出す前に結合するのではなく、出力ファイルに順番に書き込みます。CSVライターの使用を検討することもできます。
  • データを一度に1つの文字列に解凍する代わりに、BZ2Fileオブジェクトを使用してCSVリーダーに渡します。

実際にデータを解凍するループ本体は一度だけ呼び出されるようです。dataHandle.read(size)おそらく、解凍される巨大な文字列を生成する呼び出しを回避し、ファイルオブジェクトを直接操作する方法を見つけるでしょう。

補遺: BZ2Fileは、ファイル名引数が必要なため、おそらくあなたのケースには適用できません。必要なのは、ZipExtFileに匹敵する、解凍にBZ2Decompressorを使用する、統合された読み取り制限を備えたファイルオブジェクトビューのようなものです。

ここでの私の主なポイントは、データを全体として丸呑みして後で再度分割するのではなく、データのより反復的な処理を実行するようにコードを変更する必要があるということです。

于 2010-10-10T02:18:23.217 に答える