سؤال

وأود أن تحسين أداء بيثون السيناريو تم استخدام 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 هو ذات الصلة فقط إلى حد أن مقارنة أعداد ضد التهم الأخرى مثل عدد من حرف/الميادين/الخطوط في الملف قد highligh الشذوذ; tottime و cumtime ما يهم حقا. cumtime هو الوقت الذي يقضيه في وظيفة/طريقة بما في ذلك الوقت الذي يقضيه في وظائف/أساليب أنه المكالمات ؛ tottime هو الوقت الذي يقضيه في وظيفة/طريقة باستثناء الوقت الذي يقضيه في وظائف/أساليب أنه المكالمات.

أجد أنه من المفيد أن نوع احصائيات على tottime و مرة أخرى على cumtime, وليس على name.

bgchar بالتأكيد يشير إلى تنفيذ البرنامج النصي وليس في غير محلها حيث يستغرق 8.9 ثانية من 13.5;التي 8.9 ثانية لا تشمل الوقت في وظائف/أساليب أنه المكالمات!تقرأ بعناية ما @تكمن ريان يقول عن modularising السيناريو الخاص بك إلى وظائف ، وتنفيذ نصيحته.وبالمثل ما @جونزي يقول.

string ذكر لأنك import string واستخدامه في مكان واحد فقط: string.find(elements[0], 'p').على خط آخر في الإخراج ستلاحظ أن السلسلة.تجد مرة واحدة فقط ، حتى انها ليست مشكلة الأداء في تشغيل هذا البرنامج النصي.ومع ذلك:يمكنك استخدام str الطرق في كل مكان آخر. string وظائف انتقدت في الوقت الحاضر يتم تنفيذها عن طريق استدعاء المقابلة str الأسلوب.هل سيكون أفضل من الكتابة elements[0].find('p') == 0 دقيق ولكن أسرع ما يعادلها ، قد ترغب في استخدام elements[0].startswith('p') الذي من شأنه أن ينقذ القراء يتساءل ما إذا كان هذا == 0 ينبغي أن يكون في الواقع == -1.

الأساليب الأربعة التي ذكرها @بيرند Petersohn يستغرق سوى 3.7 ثانية من مجموع وقت تنفيذ 13.541 ثانية.قبل أن القلق كثيرا عن هؤلاء ، modularise السيناريو الخاص بك إلى وظائف تشغيل cProfile مرة أخرى ، وفرز احصائيات من قبل tottime.

تحديث بعد السؤال المنقحة مع تغيير البرنامج النصي:

"""السؤال:ماذا يمكنني أن أفعل حيال الانضمام تقسيم عمليات الكتابة إلى الحد من تأثير واضح على أداء هذا السيناريو؟""

؟ تلك 3 معا تأخذ 2.6 ثانية من إجمالي 13.8.الخاص بك parseJarchLine وظيفة أخذ 8.5 ثانية (والتي لا تشمل الوقت الذي تستغرقه المهام/أساليب أنه المكالمات. assert(8.5 > 2.6)

بيرند وقد سبق أن بينا في ما قد تفكر في ذلك.أنت داع تقسيم الخط تماما فقط للانضمام مجددا عند كتابة هذا التقرير.تحتاج إلى فحص العنصر الأول فقط.بدلا من elements = line.split('\t') هل elements = line.split('\t', 1) واستبدال '\t'.join(elements[1:]) قبل elements[1].

الآن دعونا الغوص في الجسم من parseJarchLine.عدد من الاستخدامات في المصدر وطريقة استخدام long المدمج في وظيفة مذهلة.مذهل أيضا هو حقيقة أن long لم يرد ذكرها في cProfile الانتاج.

لماذا تحتاج long في كل شيء ؟ ملفات أكثر من 2 جيجابايت ؟ موافق, ثم عليك أن تنظر في أن منذ الثعبان 2.2, int تجاوز أسباب الترقية long بدلا من رفع استثناء.يمكنك الاستفادة من التنفيذ أسرع من int الحسابية.تحتاج أيضا إلى النظر في أن تفعل long(x) عندما x هو بالفعل بشكل واضح على long هو مضيعة للموارد.

هنا هو parseJarchLine وظيفة مع إزالة النفايات تغييرات ملحوظة [1] و تغيير إلى الباحث تغييرات ملحوظة [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, الذي يحفظ اثنين من المتغيرات العالمية ، gazillion المكالمات وظيفة.يمكنك أيضا حفظ الملايين من عمليات البحث sys.stdout.write عن طريق وضع write = sys.stdout.write مرة واحدة أمام extractData و تستخدم بدلا من ذلك.

راجع للشغل, النصي اختبارات بايثون 2.5 أو أفضل ؛ هل حاولت التنميط على 2.5 و 2.6?

نصائح أخرى

سيكون هذا الإخراج أكثر فائدة إذا كان الرمز أكثر وحدات كما ذكر كذبة ريان. ومع ذلك ، هناك شيئان يمكنك التقاطهما من الإخراج والنظر فقط في الكود المصدري:

أنت تقوم بالكثير من المقارنات التي ليست ضرورية فعليًا في بيثون. على سبيل المثال ، بدلاً من:

if len(entryText) > 0:

يمكنك فقط الكتابة:

if entryText:

قائمة فارغة تقييم إلى خطأ في بيثون. نفس الشيء صحيح بالنسبة لسلسلة فارغة ، والتي تختبرها أيضًا في الكود الخاص بك ، وتغييرها سيجعل الرمز أقصر قليلاً وأكثر قابلية للقراءة ، لذا بدلاً من ذلك:

   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 ولكن باستخدام BZ2DecusPressor لإلغاء الضغط.

نقطتي الرئيسية هنا هي أنه يجب تغيير التعليمات البرمجية الخاصة بك لإجراء معالجة تكرارية أكثر لبياناتك بدلاً من تحطيمها ككل وتقسيمها مرة أخرى بعد ذلك.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top