Python не может распараллелить чтение буфера - PullRequest
0 голосов
/ 11 мая 2018

У меня проблемы с производительностью в многопоточности.

У меня есть фрагмент кода, который читает буфера 8 МБ параллельно:

import copy
import itertools
import threading
import time

# Basic implementation of thread pool.
# Based on multiprocessing.Pool
class ThreadPool:

   def __init__(self, nb_threads):
      self.nb_threads = nb_threads

   def map(self, fun, iter):

      if self.nb_threads <= 1:
         return map(fun, iter)
      nb_threads = min(self.nb_threads, len(iter))

      # ensure 'iter' does not evaluate lazily
      # (generator or xrange...)
      iter = list(iter)

      # map to results list
      results = [None] * nb_threads
      def wrapper(i):
         def f(args):
            results[i] = map(fun, args)
         return f

      # slice iter in chunks
      chunks = [iter[i::nb_threads] for i in range(nb_threads)]

      # create threads
      threads = [threading.Thread(target = wrapper(i), args = [chunk]) \
                 for i, chunk in enumerate(chunks)]

      # start and join threads
      [thread.start() for thread in threads]
      [thread.join() for thread in threads]

      # reorder results
      r = list(itertools.chain.from_iterable(map(None, *results)))
      return r

payload = [0] * (1000 * 1000)  # 8 MB
payloads = [copy.deepcopy(payload) for _ in range(40)]

def process(i):
   for i in payloads[i]:
      j = i + 1

if __name__ == '__main__':

   for nb_threads in [1, 2, 4, 8, 20]:

      t = time.time()
      c = time.clock()

      pool = ThreadPool(nb_threads)
      pool.map(process, xrange(40))

      t = time.time() - t
      c = time.clock() - c

      print nb_threads, t, c

Вывод:

1 1.04805707932 1.05
2 1.45473504066 2.23
4 2.01357698441 3.98
8 1.56527090073 3.66
20 1.9085559845 4.15

Почему модуль threading с треском проваливается при распараллеливании простых чтений из буфера?Это из-за GIL?Или из-за какой-то странной конфигурации на моей машине одному процессу разрешен только один доступ к ОЗУ за раз (у меня приличное ускорение, если я переключаю ThreadPool для многопроцессорную обработку. Пул это код выше)?

Я использую CPython 2.7.8 в дистрибутиве Linux.

1 Ответ

0 голосов
/ 11 мая 2018

Да, GIL Python предотвращает параллельный запуск кода Python в нескольких потоках. Вы описываете свой код как «чтение буфера», но на самом деле он выполняет произвольный код Python (в данном случае, перебирая список, добавляя 1 к другим целым числам). Если ваши потоки блокируют системные вызовы (например, чтение из файла или из сетевого сокета), то GIL обычно освобождается, пока поток блокирует ожидание внешних данных. Но поскольку большинство операций над объектами Python могут иметь побочные эффекты, вы не можете выполнять несколько из них параллельно.

Одной из важных причин этого является то, что сборщик мусора в CPython использует подсчет ссылок в качестве основного способа узнать, когда объект может быть очищен. Если несколько потоков пытаются обновить счетчик ссылок одного и того же объекта одновременно, они могут оказаться в состоянии гонки и оставить объект с неправильным счетчиком. GIL предотвращает это, поскольку только один поток может вносить такие внутренние изменения за раз. Каждый раз, когда ваш код process делает j = i + 1, он будет обновлять счетчик ссылок целочисленных объектов 0 и 1 пару раз каждый. Это именно то, что GIL существует для защиты.

...