Проведение записей с помощью словаря


Можете вы пожалуйста помочь мне с Следующий сценарий?

На данный момент скрипт занимает до 20 минут, чтобы выполнить, в зависимости от объема обрабатываемых данных (каждый раз, когда скрипт запускается, он обрабатывает несколько тысяч строк данных). Я хочу сделать скрипт более отзывчивым. Можете ли вы сказать мне, как я могу обновить скрипт для повышения производительности?

См. комментарии в каждой из основных частей сценария:

#!/opt/SP/mdp/home/SCRIPTS/tools/Python-2.6.2/bin/python

import glob
import re as regex
from datetime import datetime

# Here I am getting two set of files which I am going to use to create two separate dictionaries
Cnfiles = glob.glob('/logs/split_logs/Cn*_generic_activity.log')
Prfiles = glob.glob('/logs/split_logs/Pr*_generic_activity.log')

# Output file
log = file('/logs/split_logs/processed_data.log', 'w')

# First dictionary, holds received records
Cn = {}
for logfile in Cnfiles:
    with open(logfile) as logfile:
        filecontent = logfile.xreadlines()
        for line in filecontent:
            if 'SERV1' in line and 'RECV' in line or 'SERV2' in line and 'RECV' in line or 'SERV3' in line and 'RECV' in line:
                line = regex.sub('<._', '', line)
                line = line.replace('<', '')
                line = line.replace('>', '')
                line = line.replace('.', ' ')
                line = line.replace(r'|', ' ')
                line = line.strip()
                field = line.split(' ')
                opco = field[4]
                service = field[5]
                status = field[6]
                jarid = field[10]
                Cn.setdefault(opco, {}).setdefault(service, {}).setdefault(status, {})[jarid] = jarid

# Second dictionary, holds the various stages the records go through
Pr = {}
for logfile in Prfiles:
    with open(logfile) as logfile:
        filecontent = logfile.xreadlines()
        for line in filecontent:
            if 'status 7 to 13' in line or 'status 9 to 13' in line or 'status 7 to 14' in line or 'status 9 to 14' in line or 'status 5 to 504' in line or 'status 7 to 505' in line:
                line = line.replace('<', '')
                line = line.replace('>', '')
                line = line.replace('.', ' ')
                line = line.strip()
                field = line.split(' ')
                jarid = field[8]
                status = field[13]
                Pr.setdefault(status, {})[jarid] = jarid

# Up to this point, the script performs quite well, even with big files.
# However, the next step is comparing the two dictionaries and if it finds a record that is in both dicts,
# it is creating new sub-dictionaries to hold the various stages of the records.  This is the step
# that is taking the most time!  Do you know of a better way I can do this?
for opco in Cn.keys():
    for service in Cn[opco].keys():
        for status in Cn[opco][service].keys():
            for jarid in Cn[opco][service][status].keys():
                if jarid in Pr['13'].keys():
                    Cn[opco][service].setdefault('ACK', {})[jarid] = jarid
                elif jarid in Pr['14'].keys():
                    Cn[opco][service].setdefault('NACK', {})[jarid] = jarid
                else:
                    if jarid in Pr['504'].keys() or jarid in Pr['505'].keys():
                        Cn[opco][service].setdefault('RETRY', {})[jarid] = jarid

# Once the new sub-dictionaries are created, I am just counting the number of records in
# each one and writing the output to the log.
timestamp = (datetime.now()).strftime('%y%m%d %H:%M')
for opco in sorted(Cn.keys()):
    for service in sorted(Cn[opco].keys()):
        if 'RECV' in Cn[opco][service].keys():
            recvcount = len(Cn[opco][service]['RECV'])
        else:
            recvcount = ''
        if 'NACK' in Cn[opco][service].keys():
            nackcount = len(Cn[opco][service]['NACK'])
        else:
            nackcount = ''
        if 'ACK' in Cn[opco][service].keys():
            ackcount = len(Cn[opco][service]['ACK'])
        else:
            ackcount = ''
        if 'RETRY' in Cn[opco][service].keys():
            retrycount = len(Cn[opco][service]['RETRY'])
        else:
            retrycount = ''
        log.write('%s\t%s\t%s\t%s\t%s\t%s\t%s\n' % (timestamp, opco, service, recvcount, ackcount, nackcount, retrycount))
log.close()

Обновление

Я обновил свой скрипт, используя большинство предложений, размещенных здесь. Я использую регулярные выражения. Я не использую дикт.ключи() для перебора предсказывает больше, и я сократил мои заявления, и т. д. Однако, мой скрипт по-прежнему работает очень медленно.

    for opco in Cn:
        for service in Cn[opco]:
            for jarid in Cn[opco][service]['RECV']:
                if jarid in Pr['13']:
                    Cn[opco][service].setdefault('ACK', []).append(jarid)
                elif jarid in Pr['14']:
                    Cn[opco][service].setdefault('NACK', []).append(jarid)
                else:
                    if jarid in Pr['504'] or jarid in Pr['505']:
                        Cn[opco][service].setdefault('RETRY', []).append(jarid)

Я запустить мой скрипт с профайлером и я вижу, что этот шаг в сценарии занимает больше всего времени (1368 секунд процессора в прошлый раз я выполнил его!). Обратите внимание, что я новичок в Python и я до сих пор не имеют хорошее представление о всем, что можно сделать с ним.

Обновление

Мне удалось сделать весь мой скрипт выполнить в течение 10 секунд путем изменения 'цикл' я выложил выше на следующее:

    for opco in Cn:
        for service in Cn[opco]:
            ack = set(Cn[opco][service]['RECV']) & set(Pr['13'])
            for jarid in ack:
                Cn[opco][service].setdefault('ACK', set()).add(jarid)
            nack = set(Cn[opco][service]['RECV']) & set(Pr['14'])
            for jarid in nack:
                Cn[opco][service].setdefault('NACK', set()).add(jarid)
            retry = set(Cn[opco][service]['RECV']) & set(Pr['504'])
            for jarid in retry:
                Cn[opco][service].setdefault('RETRY', set()).add(jarid)


1047
6
задан 13 июля 2011 в 06:07 Источник Поделиться
Комментарии
5 ответов

Это:

jarid in Pr['13'].keys()

получает ключи от дикт как в виде списка, а затем выполняет поиск в списке, который теряет скорость, преимущество дикт. Поиск список О(П), в то время как ищете дикт является O(1). Это быстрее, чтобы сделать это:

jarid in Pr['13']

Это:

if 'SERV1' in line and 'RECV' in line or 'SERV2' in line and 'RECV' in line or 'SERV3' in line and 'RECV' in line:

эквивалентно этому:

if ('SERV1' in line or 'SERV2' in line or 'SERV3' in line) and 'RECV' in line:

Это:

line = line.replace('<', '')
line = line.replace('>', '')
line = line.replace('.', ' ')
line = line.replace(r'|', ' ')

можно сократить до этого:

line = regex.sub('<._', '', line)
line = regex.sub('[<>,|]', '', line)

Это:

line = line.replace('<', '')
line = line.replace('>', '')
line = line.replace('.', ' ')

можно сократить до этого:

line = regex.sub('[<>.]', '', line)

Предварительная компиляция регулярных выражений может дать небольшое увеличение скорости, но не сильно, потому что регулярные выражения являются кэшируется заново модуль.

7
ответ дан 13 июля 2011 в 07:07 Источник Поделиться

Предложения:


  • скомпилировать регулярное выражение-Эс один раз и использовать скомпилированную версию

  • перейти в лог-файлы один раз вместо трех раз.

  • найдите строки в журнале с использованием regex - я не знаю точный синтаксис, но он должен сказать: "начинается с новой строки, которая заканчивается символом новой строки и имеет ...". Затем скомпилировать его и использовать повторно.метод findAll()

  • попробовать и избежать линии = линии.заменить(..)с вас. Поскольку string является неизменным, каждый заменит создает новую строку. Может использовать индексы схемы opco = строки[Х:Y]. Я не знаю, как сложные бревна, но, я думаю, что имея несколько еслис вместо всех этих замен должен ускорить события.

  • Если я правильно понял ваш комментарий, по сути, что у вас есть две гигантские списки. Одна для "входа приемника" и "этапы" журнала. Как я это вижу (поправьте меня, если я ошибаюсь), что ты делаешь (во-первых, сокращение списков), а затем идет запись на запись на один список и проверить, если запись появляется во второй список. Возможно, (не убивайте меня, если это смешно ;) сортировка списков и перейти запись на запись на второй список при использовании двоичного поиска. Т. е.:

    Л1 = открыть("вход приемника").readlines()

    Л2 = открыть("этапы журнал").readlines()

    сортировка(Л2) # на Джерид

    для вступления в Л1:

     if binarySearch(l2, entry['jarid']) # again, on jarid
    # analyze the l2

    Теоретически говоря, ты совершаешь огромную скорость-до. Практически, я не знаю, сработает ли это. в Python есть встроенный список.сортировать() и по https://stackoverflow.com/questions/212358/binary-search-in-python это легко реализовать двоичный поиск.


4
ответ дан 13 июля 2011 в 06:07 Источник Поделиться

Во-первых, процесс ввода/вывода связан или связан с ЦП? Посмотрите на ваш счетчик процессора, когда он работает. Если это ввода/вывода, ЦП будет менее 100% и не может быть много вы можете сделать об этом, кроме как сделать лучше оборудование.

Если ты связан с ЦП, ряд вещей может помочь. Это не огромная экономия, но это поможет для предварительной компиляции регулярных выражений. Вместо
строка = регулярное выражение.суб('<._', "линия)

Начните с

regex1 = regex.compile('<._')

за пределами вашего цикла, а внутри цикла делать

line = regex1.sub('',line)

Также долго, если тесты будут выполняться быстрее, так как регулярные выражения. Вместо

if 'SERV1' in line and 'RECV' in line or 'SERV2' in line and 'RECV' in line or 'SERV3' in line and 'RECV' in line:

Делать

regex2 = regex.compile('SERV[1-3].*RECV')  # Outside of loop
if regex2.search(line):

Обработчик регулярных выражений очень умна и оптимизирована. Поскольку эта линия работает на каждой записи, она должна быть реальная экономия.

Аналогичным образом, регулярное выражение, которое захватывает только 5-го, 6-го, 7-го и 11-го полей избавит вас от выделения памяти для строк, для всех остальных, которые вы не используете. Например, чтобы захватить третье и пятое слова в строке использовать это регулярное выражение (понимание того, что матчей пробелы и матчей без пробелов и скобок, определять, что попадет в плен и хранится в матче объекта группы):

regex3 = regex.compile("^\S+\s+\S+\s+(\S+)\s+\S+\s+(\S+)")
m = regex3.search(line)
word3 = m.group(1)
word5 = m.group(2)

Намного сложнее читать, но он будет работать намного быстрее. (Комментировать свой код!) В самом деле, если вы просто замените все серии онлайн.замену и сплит с одним большим галантерейных регулярное выражение, что бы быть лучшей. Без примера того, что вход похоже, это не практично для построения регулярных выражений.

Кроме того, вы могли бы попробовать запустить профайлер.

4
ответ дан 13 июля 2011 в 06:07 Источник Поделиться

Некоторыми другими предложениями в дополнение к тем, что уже упоминалось:


  • такие вещи, как some_dict[Джерид] = Джерид говорит о том, что вы на самом деле хотите установить вместо словаря?

  • вы используете значение setdefault метод довольно много: может быть, посмотреть на коллекции.defaultdict

  • в случае СN[схемы opco][услуги]['приема'] и так далее... Вы, кажется, использовать это только, чтобы получить количество вхождений? Вам даже не придется использовать набор для этого: посмотрите на коллекции.Счетчик или использовать defaultdict(инт). (вы могли бы просто сделать что-то вроде service_counter['приема'] += 1, вы даже не должны проверить, являетесь ли вы видели ключ или нет)

  • вы делаете кучу запросов снова и снова, например для СП[схемы opco].ключи(), затем вы делаете СN[схемы opco][обслуживание].ключи(): ты смотришь вверх, спицы[схемы opco] раз, а затем вы найти службу (с ключом, которую вы только что получен), а потом вы делаете все это снова, но с дополнительный уровень, и снова... использования словаря объектов() или iteritems() метод, чтобы выполнить итерации по ключам и значениям одновременно. Например:

    for opco, opco_val in Cn.items():
    for service, service_val in opco_val.items():

    и т. д.... Таким образом, вам не придется искать его в следующий раз (и, конечно, не вся цепь!)


1
ответ дан 13 июля 2011 в 08:07 Источник Поделиться

Вы не жаловаться на эту часть кода, но я кое-что доставит некоторое неудобство:

with open(logfile) as logfile:
filecontent = logfile.xreadlines()
for line in filecontent:

# The call to xreadlines() is wasteful; You can iterate over the file descriptor directly.
# I also wouldn't use the same name for the filename and the file descriptor.

with open(logfile) as f:
for line in f:

Еще два предложения:


  • Воспользоваться лесозаготовки модуль. Таким образом, вы не должны делать вид (багги и некрасиво) пользовательской реализации здесь:

    log = file('/logs/split_logs/processed_data.log', 'w')
    log.write('%s\t%s\t%s\t%s\t%s\t%s\t%s\n' % (timestamp, opco, service, recvcount, ackcount, nackcount, retrycount))
    log.close()

  • Взгляните на ПЭП 8. Она что-то сказать о вашей линии длинные.

1
ответ дан 15 июля 2011 в 09:07 Источник Поделиться