ما هو هذا cProfile نتيجة تقول لي أنا بحاجة إلى إصلاح ؟
-
29-09-2019 - |
سؤال
وأود أن تحسين أداء بيثون السيناريو تم استخدام 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 لإلغاء الضغط.
نقطتي الرئيسية هنا هي أنه يجب تغيير التعليمات البرمجية الخاصة بك لإجراء معالجة تكرارية أكثر لبياناتك بدلاً من تحطيمها ككل وتقسيمها مرة أخرى بعد ذلك.