Оптимизируйте этот код синтаксического анализа журнала python

#python #multithreading #optimization #multiprocessing #logfiles

#python #многопоточность #оптимизация #многопроцессорность #файлы журналов

Вопрос:

Время выполнения этого кода на моем ноутбуке для входного файла объемом 4,2 ГБ составляет 48 секунд. Входной файл разделен табуляцией, каждое значение заключено в кавычки. Каждая запись заканчивается новой строкой, например '"val1"t"val2"t"val3"t..."valn"n'

Я пробовал использовать многопроцессорную обработку с 10 потоками: один для постановки строк в очередь, 8 для разбора отдельных строк и заполнения очереди вывода и один для уменьшения очереди вывода до значения defaultdict, показанного ниже, но выполнение кода заняло 300 секунд, что в 6 раз больше, чем у следующего:

 from collections import defaultdict
def get_users(log):
    users = defaultdict(int)
    f = open(log)
    # Read header line
    h = f.readline().strip().replace('"', '').split('t')
    ix_profile = h.index('profile.type')
    ix_user = h.index('profile.id')
    # If either ix_* is the last field in h, it will include a newline. 
    # That's fine for now.
    for (i, line) in enumerate(f): 
        if i % 1000000 == 0: print "Line %d" % i # progress notification

        l = line.split('t')
        if l[ix_profile] != '"7"': # "7" indicates a bad value
            # use list slicing to remove quotes
            users[l[ix_user][1:-1]]  = 1 

    f.close()
    return users
  

Я проверил, что я не привязан к вводу-выводу, удалив из цикла for все, кроме инструкции print. Этот код выполнялся за 9 секунд, что я буду считать нижней границей скорости, с которой может выполняться этот код.

Мне нужно обработать много файлов объемом 5 ГБ, так что даже небольшое улучшение во время выполнения (я знаю, я могу удалить печать!) поможет. Машина, на которой я работаю, имеет 4 ядра, поэтому я не могу не задаться вопросом, есть ли способ заставить многопоточный / многопроцессорный код выполняться быстрее, чем приведенный выше код.

Обновить:

Я переписал многопроцессорный код следующим образом:

 from multiprocessing import Pool, cpu_count
from collections import defaultdict

def parse(line, ix_profile=10, ix_user=9):
    """ix_profile and ix_user predetermined; hard-coding for expedience."""
    l = line.split('t')
    if l[ix_profile] != '"7"':
        return l[ix_user][1:-1]

def get_users_mp():
    f = open('20110201.txt')
    h = f.readline() # remove header line
    pool = Pool(processes=cpu_count())
    result_iter = pool.imap_unordered(parse, f, 100)
    users = defaultdict(int)
    for r in result_iter:
        if r is not None:
            users[r]  = 1
    return users
  

Он выполняется за 26 секунд, что в 1,85 раза быстрее. Неплохо, но с 4 ядрами, не так много, как я надеялся.

Комментарии:

1. Вероятно, самый простой способ воспользоваться преимуществами нескольких ядер — это запустить 4 копии одновременно (используя подпроцесс), затем объединить выходные данные, возможно, сохранив / загрузив фрагмент словаря.

2. Я не вижу причины, по которой это должно быть намного медленнее, чем 9 секунд. Можете ли вы предоставить образец файла, который вы читаете?

3. @Winston, в файле содержится 40 столбцов, разделенных табуляцией, с полями от 1 до 80 символов, и 8 250 000 строк. К сожалению, я не могу предоставить образец данных.

4. Рассматривали ли вы gnu cut и grep ?

5. Это был бы отличный вопрос для codereview.stackexchange.com

Ответ №1:

Используйте регулярные выражения.

Тест определяет, что дорогостоящей частью процесса является вызов str.split(). Вероятно, необходимость создавать список и кучу строковых объектов для каждой строки обходится дорого.

Во-первых, вам нужно создать регулярное выражение для сопоставления со строкой. Что-то вроде:

 expression = re.compile(r'("[^"]")t("[^"]")t')
  

Если вы вызовете expression.match(line).groups(), вы получите первые два столбца, извлеченных в виде двух строковых объектов, и вы сможете выполнять логику с ними напрямую.

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

Редактировать

из коллекций импортировать defaultdict импортировать повторно

 def get_users(log):
    f = open(log)
    # Read header line
    h = f.readline().strip().replace(''', '').split('t')
    ix_profile = h.index('profile.type')
    ix_user = h.index('profile.id')

    assert ix_user < ix_profile
  

Этот код предполагает, что пользователь находится перед профилем

     keep_field = r'"([^"]*)"'
  

Это регулярное выражение будет захватывать один столбец

     skip_field = r'"[^"]*"'
  

Это регулярное выражение будет соответствовать столбцу, но не фиксировать результаты. (Обратите внимание на отсутствие круглых скобок)

     fields = [skip_field] * len(h)
    fields[ix_profile] = keep_field
    fields[ix_user] = keep_field
  

Создайте список для всех полей и сохраните только те, которые нам важны

     del fields[max(ix_profile, ix_user) 1:]
  

Удалите все поля после тех, которые нас интересуют (для их сопоставления требуется время, и мы не заботимся о них)

     regex = re.compile(r"t".join(fields))
  

Фактически создайте регулярное выражение.

     users = defaultdict(int)
    for line in f:
        user, profile = regex.match(line).groups()
  

Извлеките два значения и выполните логику

         if profile != "7": # "7" indicates a bad value
            # use list slicing to remove quotes
            users[user]  = 1 

    f.close()
    return users
  

Комментарии:

1. мое regex-fu, по-видимому, недостаточно сильное. Данные расположены в столбцах 9 (пользователь) и 10 (тип профиля). Регулярное выражение, которое я использовал, было re.compile(‘t.*»(. )»t»7″ t’), но код был намного медленнее. Есть ли хороший способ извлечь только эти столбцы?

2. @Jason, я разместил там свой код, который показывает, как я это сделал. Ваше регулярное выражение не имеет смысла для меня, вы, кажется, проверяете 7, но разве это не приведет к ложным срабатываниям? Вероятно, это также медленнее. Вы хотите создать регулярное выражение, которое соответствует всей строке (по крайней мере, той части строки, которая вам нужна)

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

4. @Jason, неудивительно. многопоточность работает лучше, когда у вас есть большие задачи, которые вы можете разделить. Для такой небольшой задачи накладные расходы IPC преодолеют это. Однако, если у вас есть тонна этих файлов, то обработка разных файлов на четырех ядрах должна обеспечить вам приличное ускорение.

Ответ №2:

Если вы используете unix или cygwin, следующий небольшой скрипт выдаст вам частоту использования идентификатора пользователя, где profile ! = 7. Должно быть быстро.

Обновлено с помощью awk для подсчета идентификаторов пользователей

 #!/bin/bash

FILENAME="test.txt"

IX_PROFILE=`head -1 ${FILENAME} | sed -e 's/t/n/g' | nl -w 1 | grep profile.type | cut -f1`
IX_USER=`head -1 ${FILENAME} | sed -e 's/t/n/g' | nl -w 1 | grep profile.id | cut -f1`
# Just the userids
# sed 1d ${FILENAME} | cut -f${IX_PROFILE},${IX_USER} | grep -v "7" | cut -f2

# userids counted:
# sed 1d ${FILENAME} | cut -f${IX_PROFILE},${IX_USER} | grep -v "7" | cut -f2 | sort | uniq -c

# Count using awk..?
sed 1d ${FILENAME} | cut -f${IX_PROFILE},${IX_USER} | grep -v "7" | cut -f2 | awk '{ count[$1]  ; } END { for (x in count) { print x "t" count[x] } }'
  

Комментарии:

1. Мне пришлось немного подправить это, чтобы заставить его работать (часть sed -e). Но когда я все-таки заставил его работать, это заняло 4 минуты и 49 секунд. Я думаю, что сортировка (n log (n)) снижает производительность.

2. @Jason Sundram: Ну, я только что протестировал это на файле, который написал вручную, основываясь на предположениях из вашего кода. Сколько у вас уникальных идентификаторов пользователя, каков их формат?

3. существует 2,6 миллиона уникальных идентификаторов пользователей, они представляют собой буквенно-цифровые строки, подобные этой: «6a7746195b2f3cbbebde7e8e27a50d409590d7b8»

4. используя awk, это намного быстрее: 1 минута и 45 секунд. Хотя все еще медленнее, чем наивный python.

Ответ №3:

Видя, что ваш файл журнала разделен табуляцией, вы можете использовать csv модуль — с dialect='excel-tab' аргументом — для повышения производительности и удобочитаемости. Это, конечно, если вам нужно использовать Python вместо гораздо более быстрых консольных команд.

Комментарии:

1. спасибо за совет. Код, безусловно, более читаемый и понятный (10 строк, включая инструкцию import), но это занимает 88 секунд

2. @Jason: Вы не представили свой полный код после обновления многопроцессорной обработки. Что делает parse_split функция? Несмотря на это, кажется, что каждый из процессов вашего пула считывает строки из файла. Это неоптимально, потому что у вас есть несколько процессов, конкурирующих за один и тот же ресурс. Попробуйте загрузить строки в свой основной процесс и передать их рабочим процессам, используя очередь.

3. упс, parse_split предполагалось, что просто будет parse . Весь код есть. Я думал, что указание размера блока на imap_unordered смягчит некоторые разногласия (пусть каждый возьмет 100 строк, а затем разберется с ними). Чтение всего файла в память занимает намного больше времени, чем просто прокрутка по нему, поэтому я не уверен, что это приведет к увеличению производительности. Я попробую это и дам вам знать.

Ответ №4:

Если использование регулярных выражений может значительно ускорить процесс, игнорируя конец строки, который не нужно разделять, возможно, может помочь более простой подход:

 [snip)
ix_profile = h.index('profile.type')
ix_user = h.index('profile.id')
maxsplits = max(ix_profile, ix_user)   1 #### new statement ####
# If either ix_* is the last field in h, it will include a newline. 
# That's fine for now.
for (i, line) in enumerate(f): 
    if i % 1000000 == 0: print "Line %d" % i # progress notification
    l = line.split('t', maxsplits) #### changed line ####
[snip]
  

Пожалуйста, уделите этому особое внимание в своих данных.

Ответ №5:

Я понимаю, что у меня была почти точно такая же идея, как у Уинстона Эверта: создание регулярного выражения.

Но мое регулярное выражение:

  • выполняется для случаев, в которых ix_profile < ix_user а также для случаев, в которых ix_profile > ix_user

  • регулярное выражение фиксирует только столбец пользователя: столбцу профиля соответствует вложенный шаблон, '"(?!7")[^trn"]*"' который не соответствует, если в этом столбце присутствует «7»; таким образом, мы получаем только правильного пользователя с единственной определенной группой

.

Кроме того, я протестировал несколько алгоритмов сопоставления и извлечения:

1) с помощью re.finditer()

2) с помощью re.match() и регулярного выражения, соответствующего 40 полям

3) с re.match () и регулярным выражением, соответствующим только максимальным (ix_profile, ix_user) 1 полям

4) как 3, но с простым словарем вместо экземпляра defaultdict

Для измерения времени мой код создает файл на основе предоставленной вами информации о его содержимом.

.

Я протестировал 4 следующие функции в 4 кодах:

1

 def get_users_short_1(log):
    users_short = defaultdict(int)
    f = open(log)
    # Read header line
    h = f.readline().strip().replace('"', '').split('t')
    ix_profile = h.index('profile.type')
    ix_user = h.index('profile.id')
    # If either ix_* is the last field in h, it will include a newline. 
    # That's fine for now.

    glo = 40*['[^t]*']
    glo[ix_profile] = '"(?!7")[^t"] "'
    glo[ix_user] = '"([^t"]*)"'
    glo[39] = '"[^trn]*"'
    regx = re.compile('^' 't'.join(glo),re.MULTILINE)

    content = f.read()
    for mat in regx.finditer(content):
        users_short[mat.group(1)]  = 1

    f.close()
    return users_short
  

2

 def get_users_short_2(log):
    users_short = defaultdict(int)
    f = open(log)
    # Read header line
    h = f.readline().strip().replace('"', '').split('t')
    ix_profile = h.index('profile.type')
    ix_user = h.index('profile.id')
    # If either ix_* is the last field in h, it will include a newline. 
    # That's fine for now.

    glo = 40*['[^t]*']
    glo[ix_profile] = '"(?!7")[^t"]*"'
    glo[ix_user] = '"([^t"]*)"'
    regx = re.compile('t'.join(glo))


    for line in f:
        gugu = regx.match(line)
        if gugu:
            users_short[gugu.group(1)]  = 1
    f.close()
    return users_short
  

3

 def get_users_short_3(log):
    users_short = defaultdict(int)
    f = open(log)
    # Read header line
    h = f.readline().strip().replace('"', '').split('t')
    ix_profile = h.index('profile.type')
    ix_user = h.index('profile.id')
    # If either ix_* is the last field in h, it will include a newline. 
    # That's fine for now.

    glo = (max(ix_profile,ix_user)   1) * ['[^t]*']
    glo[ix_profile] = '"(?!7")[^t"]*"'
    glo[ix_user] = '"([^t"]*)"'
    regx = re.compile('t'.join(glo))

    for line in f:
        gugu = regx.match(line)
        if gugu:
            users_short[gugu.group(1)]  = 1

    f.close()
    return users_short
  

4

Полный код 4, который кажется самым быстрым:

 import re
from random import choice,randint,sample
import csv
import random
from time import clock

choi = 1
if choi:
    ntot = 1000
    chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
    def ry(a=30,b=80,chars=chars,nom='abcdefghijklmnopqrstuvwxyz'):
        if a==30:
            return ''.join(choice(chars) for i in xrange(randint(30,80)))
        else:
            return ''.join(choice(nom) for i in xrange(randint(8,12)))

    num = sample(xrange(1000),200)
    num.sort()
    print 'num==',num
    several = [e//3 for e in xrange(0,800,7) if e//3 not in num]
    print
    print 'several==',several

    with open('biggy.txt','w') as f:
        head = ('aaa','bbb','ccc','ddd','profile.id','fff','ggg','hhhh','profile.type','iiii',
                'jjj','kkkk','lll','mmm','nnn','ooo','ppp','qq','rr','ss',
                'tt','uu','vv','ww','xx','yy','zz','razr','fgh','ty',
                'kfgh','zer','sdfs','fghf','dfdf','zerzre','jkljkl','vbcvb','kljlk','dhhdh')
        f.write('t'.join(head) 'n')
        for i in xrange(1000):
            li = [ ry(a=8).join('""') if n==4 else ry().join('""')
                   for n in xrange(40) ]
            if i in num:
                li[4] = '@#~amp;=*;'
                li[8] = '"7"'
            if i in several:
                li[4] = '"BRAD"'
            f.write('t'.join(li) 'n')



from collections import defaultdict
def get_users(log):
    users = defaultdict(int)
    f = open(log)
    # Read header line
    h = f.readline().strip().replace('"', '').split('t')
    ix_profile = h.index('profile.type')
    ix_user = h.index('profile.id')
    # If either ix_* is the last field in h, it will include a newline. 
    # That's fine for now.
    for (i, line) in enumerate(f): 
        #if i % 1000000 == 0: print "Line %d" % i # progress notification

        l = line.split('t')
        if l[ix_profile] != '"7"': # "7" indicates a bad value
            # use list slicing to remove quotes

            users[l[ix_user][1:-1]]  = 1 
    f.close()
    return users




def get_users_short_4(log):
    users_short = {}
    f = open(log)
    # Read header line
    h = f.readline().strip().replace('"', '').split('t')
    ix_profile = h.index('profile.type')
    ix_user = h.index('profile.id')
    # If either ix_* is the last field in h, it will include a newline. 
    # That's fine for now.

    glo = (max(ix_profile,ix_user)   1) * ['[^t]*']
    glo[ix_profile] = '"(?!7")[^t"]*"'
    glo[ix_user] = '"([^t"]*)"'
    regx = re.compile('t'.join(glo))

    for line in f:
        gugu = regx.match(line)
        if gugu:
            gugugroup = gugu.group(1)
            if gugugroup in users_short:
                users_short[gugugroup]  = 1
            else:
                users_short[gugugroup] = 1

    f.close()
    return users_short




print 'nn'

te = clock()
USERS = get_users('biggy.txt')
t1 = clock()-te

te = clock()
USERS_short_4 = get_users_short_4('biggy.txt')
t2 = clock()-te



if choi:
    print 'nlen(num)==',len(num),' : number of lines with ix_profile=='"7"''
    print "USERS['BRAD']==",USERS['BRAD']
    print 'then :'
    print str(ntot) ' lines - ' str(len(num)) ' incorrect - ' str(len(several)) 
          ' identical   1 user BRAD = ' str(ntot - len(num)-len(several) 1)    
print 'nlen(USERS)==',len(USERS)
print 'len(USERS_short_4)==',len(USERS_short_4)
print 'USERS == USERS_short_4 is',USERS == USERS_short_4

print 'n----------------------------------------'
print 'time of get_users() :n', t1,'n----------------------------------------'
print 'time of get_users_short_4 :n', t2,'n----------------------------------------'
print 'get_users_short_4() / get_users() = ' str(100*t2/t1)  ' %'
print '----------------------------------------'
  

Один из результатов этого кода 4 приведен в качестве примера:

 num== [2, 12, 16, 23, 26, 33, 38, 40, 43, 45, 51, 53, 84, 89, 93, 106, 116, 117, 123, 131, 132, 135, 136, 138, 146, 148, 152, 157, 164, 168, 173, 176, 179, 189, 191, 193, 195, 199, 200, 208, 216, 222, 224, 227, 233, 242, 244, 245, 247, 248, 251, 255, 256, 261, 262, 266, 276, 278, 291, 296, 298, 305, 307, 308, 310, 312, 314, 320, 324, 327, 335, 337, 340, 343, 350, 356, 362, 370, 375, 379, 382, 385, 387, 409, 413, 415, 419, 433, 441, 443, 444, 446, 459, 462, 474, 489, 492, 496, 505, 509, 511, 512, 518, 523, 541, 546, 548, 550, 552, 558, 565, 566, 572, 585, 586, 593, 595, 601, 609, 610, 615, 628, 632, 634, 638, 642, 645, 646, 651, 654, 657, 660, 662, 665, 670, 671, 680, 682, 687, 688, 690, 692, 695, 703, 708, 716, 717, 728, 729, 735, 739, 741, 742, 765, 769, 772, 778, 790, 792, 797, 801, 808, 815, 825, 828, 831, 839, 849, 858, 859, 862, 864, 872, 874, 890, 899, 904, 906, 913, 916, 920, 923, 928, 941, 946, 947, 953, 955, 958, 959, 961, 971, 975, 976, 979, 981, 985, 989, 990, 999]

several== [0, 4, 7, 9, 11, 14, 18, 21, 25, 28, 30, 32, 35, 37, 39, 42, 44, 46, 49, 56, 58, 60, 63, 65, 67, 70, 72, 74, 77, 79, 81, 86, 88, 91, 95, 98, 100, 102, 105, 107, 109, 112, 114, 119, 121, 126, 128, 130, 133, 137, 140, 142, 144, 147, 149, 151, 154, 156, 158, 161, 163, 165, 170, 172, 175, 177, 182, 184, 186, 196, 198, 203, 205, 207, 210, 212, 214, 217, 219, 221, 226, 228, 231, 235, 238, 240, 249, 252, 254, 259, 263]




len(num)== 200  : number of lines with ix_profile=='"7"'
USERS['BRAD']== 91
then :
1000 lines - 200 incorrect - 91 identical   1 user BRAD = 710

len(USERS)== 710
len(USERS_short_4)== 710
USERS == USERS_short_4 is True

----------------------------------------
time of get_users() :
0.0788686830309 
----------------------------------------
time of get_users_short_4 :
0.0462885646081 
----------------------------------------
get_users_short_4() / get_users() = 58.690677756 %
----------------------------------------
  

Но результаты более или менее изменчивы. Я получил:

 get_users_short_1() / get_users() = 82.957476637 %
get_users_short_1() / get_users() = 82.3987686867 %
get_users_short_1() / get_users() = 90.2949842932 %
get_users_short_1() / get_users() = 78.8063007461 %
get_users_short_1() / get_users() = 90.4743181768 %
get_users_short_1() / get_users() = 81.9635560003 %
get_users_short_1() / get_users() = 83.9418269406 %
get_users_short_1() / get_users() = 89.4344442255 %


get_users_short_2() / get_users() = 80.4891442088 %
get_users_short_2() / get_users() = 69.921943776 %
get_users_short_2() / get_users() = 81.8006709304 %
get_users_short_2() / get_users() = 83.6270772928 %
get_users_short_2() / get_users() = 97.9821084403 %
get_users_short_2() / get_users() = 84.9307558629 %
get_users_short_2() / get_users() = 75.9384820018 %
get_users_short_2() / get_users() = 86.2964748485 %


get_users_short_3() / get_users() = 69.4332754744 %
get_users_short_3() / get_users() = 58.5814726668 %
get_users_short_3() / get_users() = 61.8011476831 %
get_users_short_3() / get_users() = 67.6925083362 %
get_users_short_3() / get_users() = 65.1208124156 %
get_users_short_3() / get_users() = 72.2621727569 %
get_users_short_3() / get_users() = 70.6957501222 %
get_users_short_3() / get_users() = 68.5310031226 %
get_users_short_3() / get_users() = 71.6529128259 %
get_users_short_3() / get_users() = 71.6153554073 %
get_users_short_3() / get_users() = 64.7899044975 %
get_users_short_3() / get_users() = 72.947531363 %
get_users_short_3() / get_users() = 65.6691965629 %
get_users_short_3() / get_users() = 61.5194374401 %
get_users_short_3() / get_users() = 61.8396133666 %
get_users_short_3() / get_users() = 71.5447862466 %
get_users_short_3() / get_users() = 74.6710538858 %
get_users_short_3() / get_users() = 72.9651233485 %



get_users_short_4() / get_users() = 65.5224210767 %
get_users_short_4() / get_users() = 65.9023813161 %
get_users_short_4() / get_users() = 62.8055210129 %
get_users_short_4() / get_users() = 64.9690049062 %
get_users_short_4() / get_users() = 61.9050866134 %
get_users_short_4() / get_users() = 65.8127125992 %
get_users_short_4() / get_users() = 66.8112344201 %
get_users_short_4() / get_users() = 57.865635278 %
get_users_short_4() / get_users() = 62.7937713964 %
get_users_short_4() / get_users() = 66.3440149528 %
get_users_short_4() / get_users() = 66.4429530201 %
get_users_short_4() / get_users() = 66.8692388625 %
get_users_short_4() / get_users() = 66.5949137537 %
get_users_short_4() / get_users() = 69.1708488794 %
get_users_short_4() / get_users() = 59.7129743801 %
get_users_short_4() / get_users() = 59.755297387 %
get_users_short_4() / get_users() = 60.6436352185 %
get_users_short_4() / get_users() = 64.5023727945 %
get_users_short_4() / get_users() = 64.0153937511 %
  

.

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

.

.

РЕДАКТИРОВАТЬ 1

С

 def get_users_short_Machin(log):
    users_short = defaultdict(int)
    f = open(log)
    # Read header line
    h = f.readline().strip().replace('"', '').split('t')
    ix_profile = h.index('profile.type')
    ix_user = h.index('profile.id')
    maxsplits = max(ix_profile, ix_user)   1
    # If either ix_* is the last field in h, it will include a newline. 
    # That's fine for now.
    for line in f: 
        #if i % 1000000 == 0: print "Line %d" % i # progress notification
        l = line.split('t', maxsplits)
        if l[ix_profile] != '"7"': # "7" indicates a bad value
            # use list slicing to remove quotes
            users_short[l[ix_user][1:-1]]  = 1 
    f.close()
    return users_short
  

У меня есть

 get_users_short_Machin() / get_users() = 60.6771821308 %
get_users_short_Machin() / get_users() = 71.9300992989 %
get_users_short_Machin() / get_users() = 85.1695214715 %
get_users_short_Machin() / get_users() = 72.7722233685 %
get_users_short_Machin() / get_users() = 73.6311173237 %
get_users_short_Machin() / get_users() = 86.0848484053 %
get_users_short_Machin() / get_users() = 75.1661981729 %
get_users_short_Machin() / get_users() = 72.8888452474 %
get_users_short_Machin() / get_users() = 76.7185685993 %
get_users_short_Machin() / get_users() = 82.7007096958 %
get_users_short_Machin() / get_users() = 71.1678957888 %
get_users_short_Machin() / get_users() = 71.9845835126 %
  

Используя простой dict:

 users_short = {}
.......
for line in f: 
    #if i % 1000000 == 0: print "Line %d" % i # progress notification
    l = line.split('t', maxsplits)
    if l[ix_profile] != '"7"': # "7" indicates a bad value
        # use list slicing to remove quotes
        us = l[ix_user][1:-1]
        if us not in users_short:
            users_short[us] = 1
        else:
            users_short[us]  = 1
  

improves a little the execution’s time but it remains higher than my last code 4

 get_users_short_Machin2() / get_users() = 71.5959919389 %
get_users_short_Machin2() / get_users() = 71.6118864535 %
get_users_short_Machin2() / get_users() = 66.3832514274 %
get_users_short_Machin2() / get_users() = 68.0026407277 %
get_users_short_Machin2() / get_users() = 67.9853921552 %
get_users_short_Machin2() / get_users() = 69.8946203037 %
get_users_short_Machin2() / get_users() = 71.8260030248 %
get_users_short_Machin2() / get_users() = 78.4243267003 %
get_users_short_Machin2() / get_users() = 65.7223734428 %
get_users_short_Machin2() / get_users() = 69.5903935612 %
  

.

EDIT 2

The fastest:

 def get_users_short_CSV(log):
    users_short = {}
    f = open(log,'rb')
    rid = csv.reader(f,delimiter='t')
    # Read header line
    h = rid.next()
    ix_profile = h.index('profile.type')
    ix_user = h.index('profile.id')
    # If either ix_* is the last field in h, it will include a newline. 
    # That's fine for now.

    glo = (max(ix_profile,ix_user)   1) * ['[^t]*']
    glo[ix_profile] = '"(?!7")[^trn"]*"'
    glo[ix_user] = '"([^trn"]*)"'
    regx = re.compile('t'.join(glo))

    for line in f:
        gugu = regx.match(line)
        if gugu:
            gugugroup = gugu.group(1)
            if gugugroup in users_short:
                users_short[gugugroup]  = 1
            else:
                users_short[gugugroup] = 1

    f.close()
    return users_short
  

Результат

 get_users_short_CSV() / get_users() = 31.6443901114 %
get_users_short_CSV() / get_users() = 44.3536176134 %
get_users_short_CSV() / get_users() = 47.2295100511 %
get_users_short_CSV() / get_users() = 45.4912200716 %
get_users_short_CSV() / get_users() = 63.7997241038 %
get_users_short_CSV() / get_users() = 43.5020255488 %
get_users_short_CSV() / get_users() = 40.9188320386 %
get_users_short_CSV() / get_users() = 43.3105062139 %
get_users_short_CSV() / get_users() = 59.9184895288 %
get_users_short_CSV() / get_users() = 40.22047881 %
get_users_short_CSV() / get_users() = 48.3615872543 %
get_users_short_CSV() / get_users() = 47.0374831251 %
get_users_short_CSV() / get_users() = 44.5268626789 %
get_users_short_CSV() / get_users() = 53.1690205938 %
get_users_short_CSV() / get_users() = 43.4022458372 %
  

.

ПРАВКА 3

Я протестировал get_users_short_CSV () с 10000 строками в файле вместо всего 1000:

 len(num)== 2000  : number of lines with ix_profile=='"7"'
USERS['BRAD']== 95
then :
10000 lines - 2000 incorrect - 95 identical   1 user BRAD = 7906

len(USERS)== 7906
len(USERS_short_CSV)== 7906
USERS == USERS_short_CSV is True

----------------------------------------
time of get_users() :
0.794919186656 
----------------------------------------
time of get_users_short_CSV :
0.358942826532 
----------------------------------------
get_users_short_CSV() / get_users() = 41.5618307521 %

get_users_short_CSV() / get_users() = 42.2769300584 %
get_users_short_CSV() / get_users() = 45.154631132 %
get_users_short_CSV() / get_users() = 44.1596819482 %
get_users_short_CSV() / get_users() = 30.3192350266 %
get_users_short_CSV() / get_users() = 34.4856637748 %
get_users_short_CSV() / get_users() = 43.7461535628 %
get_users_short_CSV() / get_users() = 41.7577246935 %
get_users_short_CSV() / get_users() = 41.9092878608 %
get_users_short_CSV() / get_users() = 44.6772360665 %
get_users_short_CSV() / get_users() = 42.6770989413 %
  

Ответ №6:

Может быть, вы можете сделать

 users[l[ix_user]]  = 1 
  

вместо

 users[l[ix_user][1:-1]]  = 1 
  

и удалите кавычки в dict в конце. Должно сэкономить некоторое время.

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

Или прочитайте решение в этой статье, поскольку он, похоже, делает что-то очень похожее на то, что вы пытаетесь сделать.

Комментарии:

1. @Claudio, удаление нарезки не имеет существенного значения (на самом деле это добавляет 2 секунды по причинам, которые мне не ясны)

Ответ №7:

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

Из-за ограничения, согласно которому только один поток может фактически использовать интерпретатор в любой момент времени, тот факт, что у вас несколько ядер, вам не поможет. На самом деле, это может на самом деле сделать все намного хуже из-за некоторых патологических взаимодействий между двумя потоками, пытающимися получить GIL. Если вы хотите придерживаться Python, у вас есть один из двух вариантов:

  1. Попробуйте использовать Python 3.2 (или выше, 3.0 не будет работать). У него совершенно другой способ работы с GIL, который в ряде случаев устраняет проблему замедления работы с многопоточностью. Я предполагаю, что вы не используете Python 3 series, поскольку используете старую print инструкцию.
  2. Используйте процессы вместо потоков. Поскольку процессы совместно используют дескрипторы открытых файлов, вам на самом деле не нужно передавать какое-либо состояние между процессами, как только вы действительно начнете использовать файл (вы могли бы использовать каналы или сообщения, если вам действительно нужно). Это несколько увеличит время начального запуска, поскольку для создания процессов требуется больше времени, чем для потоков, но вы избежите проблемы с GIL.

Если вам нужна дополнительная информация об этой удивительно загадочной части Python, посмотрите обсуждения, связанные с GIL, на этой странице:http://www.dabeaz.com/talks.html .

Комментарии:

1. Я явно использую многопроцессорность, чтобы обойти проблемы GIL с потоками.