Сократите время выполнения при создании огромного списка

Я довольно новичок в Python, и я пытаюсь написать некоторые огромные списки (со случайными буквами внутри). На самом деле мне требуется около 75 – 80 секунд на моей машине для 2 000 000 строк.

import timeit import random, string global_tab = [] global_nb_loop = 2000000 print("Generate %d lines" % global_nb_loop) global_tab = [] for x in range(global_nb_loop): global_tab.append(("".join( [random.choice(string.ascii_letters) for i in range(15)] ), "".join( [random.choice(string.digits) for i in range(2)]))) print("%d lines generated" % len(global_tab)) 

И результат с командой linux time :

 $ time python3 DEV/PyETL/generateList.py Generate 2000000 lines 2000000 lines generated real 1m16.844s user 1m16.609s sys 0m0.203s 

Я был удивлен, наблюдая за системными ресурсами, что только 1 ядро ​​было на 100%, а не 4, как на Windows-машине, на которой я тоже это испытал.

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

Вот новый код:

 import random, string import threading global_tab = [] global_nb_threads = 4 global_nb_loop = 2000000 threadLock = threading.Lock() class generateList(threading.Thread): def __init__(self, name): threading.Thread.__init__(self) self.name = name def run(self): global global_tab self.tab = [] print("[%s] Generate %d lines" % (self.name, int(global_nb_loop/global_nb_threads))) # divide desirated lines with number of threads for x in range(int(global_nb_loop/global_nb_threads)): self.tab.append(("".join( [random.choice(string.ascii_letters) for i in range(15)] ), "".join( [random.choice(string.digits) for i in range(2)]))) threadLock.acquire() global_tab += self.tab threadLock.release() del self.tab print("[%s] %d lines in list" % (self.name, len(global_tab))) for i in range(global_nb_threads): # Create threads t = generateList("Thread-" + str(i)) # Start t.start() for i in range(global_nb_threads): # Wait for threads end t.join() 

И исполнение:

 $ time python3 DEV/PyETL/generateListThreads.py [Thread-0] Generate 500000 lines [Thread-1] Generate 500000 lines [Thread-2] Generate 500000 lines [Thread-3] Generate 500000 lines [Thread-3] 500000 lines in list [Thread-0] 1000000 lines in list [Thread-2] 1500000 lines in list [Thread-1] 2000000 lines in list real 1m40.858s user 1m41.208s sys 0m0.916s 

32 секунды более 1 ядро ​​со 100%, но мониторинг показывает, что 8 ядер были с нагрузкой 20-40% одновременно.

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

3 Solutions collect form web for “Сократите время выполнения при создании огромного списка”

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

global_tab += self.tab (я думаю) атомный через Python GIL. (На самом деле, это только претензии list.extend() , поэтому используйте это вместо. Вот еще одна ссылка: Безопасны ли list.extend() списков?

В качестве альтернативы, я бы попробовал multiprocessing.imap_unordered с большим chunksize. Недостатком является то, что результаты передаются потоком, но ваша случайная обработка строк может затмить это.

 import multiprocessing import random import string def randomword(x): return ''.join(random.choice(string.ascii_letters) for i in range(15)) pool = multiprocessing.Pool(8) results = pool.imap_unordered(randomword, range(100)) print([r for r in results]) 

Для 2 миллионов строк (я изменил его, чтобы напечатать длину):

 $ time python r.py 2000000 real 0m38.305s user 1m31.717s sys 0m25.853s 

Я также попытался немного очистить вашу версию и получил:

 $ time python rr.py [Thread-0] Generate 250000 lines [Thread-1] Generate 250000 lines [Thread-2] Generate 250000 lines [Thread-3] Generate 250000 lines [Thread-4] Generate 250000 lines [Thread-5] Generate 250000 lines [Thread-6] Generate 250000 lines [Thread-7] Generate 250000 lines [Thread-4] 250000 lines in list [Thread-1] 500000 lines in list [Thread-7] 750000 lines in list [Thread-0] 1000000 lines in list [Thread-6] 1250000 lines in list [Thread-2] 1500000 lines in list [Thread-3] 1750000 lines in list [Thread-5] 2000000 lines in list real 0m22.113s user 0m24.969s sys 0m5.537s 

Несколько существенных изменений:

  • используйте xrange() на больших диапазонах (ah, python3 уже делает это.)
  • удалить резьбовое соединение
  • используйте extend() для глобального.

(мои результаты были примерно одинаковыми, когда просто global_tab к global_tab , кстати, и оставляя временный список.)

 import random, string import threading global_tab = [] global_nb_threads = 8 global_nb_loop = 2000000 class generateList(threading.Thread): def __init__(self, name): threading.Thread.__init__(self) self.name = name def run(self): global global_tab self.tab = [] print("[%s] Generate %d lines" % (self.name, int(global_nb_loop/global_nb_threads))) for x in range(int(global_nb_loop/global_nb_threads)): self.tab.append(("".join( [random.choice(string.ascii_letters) for i in range(15)] ), "".join( [random.choice(string.digits) for i in range(2)]))) global_tab.extend(self.tab) print("[%s] %d lines in list" % (self.name, len(global_tab))) for i in range(global_nb_threads): t = generateList("Thread-" + str(i)) t.start() for i in range(global_nb_threads): t.join() 

… но однопоточное все еще немного быстрее на 16 секунд.

Если я настрою multiprocessing , я могу получить ее до 6 секунд:

 size = 2000000 processes = 8 pool = multiprocessing.Pool(processes) results = [r for r in pool.imap_unordered(randomword, range(size), chunksize=int(size/processes))] print(len(results)) 

вывод:

 $ time python r.py 2000000 real 0m5.713s user 0m35.594s sys 0m0.546s 

… поэтому я думаю, что это мой окончательный ответ: используйте multiprocessing .

Из python threading docs :

Подробности реализации CPython: в CPython из-за блокировки Global Interpreter Lock только один поток может одновременно выполнять код Python (даже если некоторые ориентированные на производительность библиотеки могут преодолеть это ограничение). Если вы хотите, чтобы ваше приложение лучше использовало вычислительные ресурсы многоядерных машин, вам рекомендуется использовать многопроцессорную обработку. Тем не менее, потоки по-прежнему являются подходящей моделью, если вы хотите одновременно запускать несколько задач, связанных с вводом-выводом.

В основном это означает, что потоки в python не улучшают производительность, если потоки в основном не ждут чего-то. Многопроцессорность работает очень хорошо в python, но поскольку процессы не разделяют какие-либо объекты или глобальное состояние, модель программирования немного отличается для многопроцессорности. Вот пример того, как вы можете использовать многопроцессорность:

 import multiprocessing import random import string def randomData(i): data = ("".join(random.sample(string.ascii_letters, 15)), "".join(random.sample(string.digits, 2))) return data global_nb_loop = 2000000 pool = multiprocessing.Pool(8) results = pool.imap(randomData, xrange(global_nb_loop)) global_tab = list(results) print len(global_tab) 

multiprocessing модуль имеет множество версий map и apply , т.е. imap , map_async и т. Д. Просмотрите документы, чтобы найти тот, который лучше всего подходит для вашей проблемы.

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

Вот версия, которая работает в 3 раза быстрее, чем исходная проблема (для справки, исходная версия запустилась через 30,3 с на моей машине).

 import numpy as np def numpy_test(N=2000000): global_nb_loop = N global_tab = [] asc_list = list('abcdefghijklmnopqrstuvwxyz') print("Generate %d lines" % global_nb_loop) global_tab = [(u.tostring(),str(v)) for u,v in zip( np.random.choice(asc_list, (N, 15)), np.random.randint(10, 100, N) )] print("%d lines generated" % len(global_tab)) In [306]: %timeit numpy_test() Generate 2000000 lines 2000000 lines generated Generate 2000000 lines 2000000 lines generated Generate 2000000 lines 2000000 lines generated Generate 2000000 lines 2000000 lines generated 1 loop, best of 3: 11.1 s per loop 
  • Как использовать numpy в необязательном наборе текста
  • Python не интернирует строки в интерактивном режиме?
  • Странное поведение Pywin32 при использовании слова
  • Что-то о `namedtuple` изменилось в 3.5.1?
  • Как я могу периодически выполнять функцию с asyncio?
  • Как очистить несколько сайтов с помощью pyqt4, изменение области?
  • Самосознание аннотаций типа в Python
  • Должен ли класс преобразовывать типы параметров во время инициализации? Если да, то как?
  • Как создать автономный исполняемый файл из скриптов python 3.5?
  • Как вы обновляетесь до последней версии python 3.5.1 на малиновом pi?
  • UnicodeDecodeError, utf-8 недопустимый байт продолжения
  • Python - лучший язык программирования в мире.