Что это за cprofile результат, говорящий мне, что мне нужно исправить?
-
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
актуальны только в той мере, в которой сравнение чисел в отношении других количеств, таких как количество символов / полей / строк в файле, могут высокими аномалиями; tottime
и cumtime
это то, что действительно имеет значение. cumtime
это время, проведенное в функции/методе в том числе время, проведенное в функциях / методах, которые он называет; tottime
это время, проведенное в функции/методе без учета время, проведенное в функциях/методах, которые он вызывает.
Я нахожу полезным сортировать статистику на tottime
и снова дальше cumtime
, не на name
.
bgchar
определенно относится к выполнению сценария и не является неактуальным, поскольку он занимает 8,9 секунды из 13,5; Это 8,9 секунды не включают в себя время в функции / методы, которые он называет! Читайте внимательно, что @Lie Ryan говорит о модульском вашим сценарии в функции и реализовать его советы. Точно так же, что говорит @josesy.
string
упоминается, потому что вы import string
и использовать его только в одном месте: string.find(elements[0], 'p')
. Анкет На другой строке на выходе вы заметите, что String.find назывался только один раз, поэтому это не проблема с производительностью в этом прогоне этого сценария. Однако: вы используете str
Методы везде. string
функции устарели в настоящее время и реализуются путем вызова соответствующего str
метод Тебе лучше писать elements[0].find('p') == 0
Для точного, но более быстрых эквивалентных и может использовать elements[0].startswith('p')
что спасет читателей, удивляющихся ли это == 0
на самом деле должен быть == -1
.
Четыре метода, упомянутые @bernd Petersohn, занимают всего 3,7 секунды из общего времени исполнения 13,541 секунды. Прежде чем слишком беспокоиться об этом, модульна свой скрипт в функции, снова запустите 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]
.
Теперь давайте погрузимся в тело парсеярхлина. Количество используемых в источнике и способе использования long
Встроенная функция удивительна. Также удивительный это тот факт, что long
не упоминается на выходе Cprofile.
Почему вам нужно long
вообще? Файлы более 2 ГБ? Хорошо, тогда вам нужно рассмотреть это с момента Python 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
и используя это вместо этого.
BTW, скрипт тесты для Python 2.5 или лучше; Вы пробовали профилирование на 2,5 и 2.6?
Другие советы
Этот выход будет более полезен, если ваш код будет более модульным, поскольку указывалось, как лежат Райан. Однако пара вещей вы можете забрать с вывода и просто глядя на исходный код:
Вы делаете много сравнений, которые на самом деле не нужны в Python. Например, вместо:
if len(entryText) > 0:
Вы можете просто написать:
if entryText:
Пустой список оценивает ложь в Python. То же самое верно для пустой строки, которую вы также проверяете в своем коде, и изменяя его также сделать код немного более коротким и читаемым, поэтому вместо этого:
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 Writer.
- Вместо одновременно распаковывать данные в одну строку, используйте объект BZ2File и пропустите, чтобы к читателю CSV.
Кажется, что корпус петли, который на самом деле не раскалывает данные, вызывается только один раз. Возможно, вы найдете способ избежать звонка dataHandle.read(size)
, который создает огромную строку, которая затем распадается, и напрямую работать с объектом файла.
Дополнение: Bz2file, вероятно, не применим в вашем случае, потому что он требует аргумента имени файла. Что вам нужно, так это что -то вроде представления файла с интегрированным пределом чтения, сравнимого с ZipextFile, но с использованием BZ2Decompressor для декомпрессии.
Моя главная точка здесь состоит в том, что ваш код должен быть изменен для выполнения более итеративной обработки ваших данных вместо того, чтобы снова провалиться в целом и снова расщепляться.