Многопоточность Python медленнее, чем последовательный?

Я пытаюсь понять многопоточное программирование в Python. Вот простая задача, с которой я хочу сравнить последовательные и параллельные скорости.

import threading
import Queue
import time
import math

def sinFunc(offset, n):
  result = []
  for i in range(n):
    result.append(math.sin(offset + i * i))
  return result

def timeSerial(k, n):
  t1 = time.time()    
  answers = []
  for i in range(k):
    answers.append(sinFunc(i, n))
  t2 = time.time()
  print "Serial time elapsed: %f" % (t2-t1)

class Worker(threading.Thread):

  def __init__(self, queue, name):
    self.__queue = queue
    threading.Thread.__init__(self)
    self.name = name

  def process(self, item):
    offset, n = item
    self.__queue.put(sinFunc(offset, n))
    self.__queue.task_done()
    self.__queue.task_done()

  def run(self):
    while 1:
        item = self.__queue.get()
        if item is None:
            self.__queue.task_done()
            break
        self.process(item)

def timeParallel(k, n, numThreads):
  t1 = time.time()    
  queue = Queue.Queue(0)
  for i in range(k):
    queue.put((i, n))
  for i in range(numThreads):
    queue.put(None)    
  for i in range(numThreads):
    Worker(queue, i).start()
  queue.join()
  t2 = time.time()
  print "Serial time elapsed: %f" % (t2-t1)

if __name__ == '__main__':

  n = 100000
  k = 100
  numThreads = 10

  timeSerial(k, n)
  timeParallel(k, n, numThreads)

#Serial time elapsed: 2.350883
#Serial time elapsed: 2.843030

Может кто-нибудь объяснить мне, что происходит? Я привык к C++, и аналогичная версия этого с использованием модуля видит ускорение, которое мы ожидаем.

3 ответа

Решение

Другие ответы касались вопроса о том, что GIL является проблемой в cpython. Но я чувствовал, что было немного недостающей информации. Это вызовет проблемы с производительностью в ситуациях, когда код, который вы запускаете в потоках, связан с процессором. В вашем случае, да, выполнение большого количества вычислений в потоках, скорее всего, приведет к значительному снижению производительности.

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

import os

def sinFunc(offset, n):
  result = []
  for i in xrange(n):
    result.append(math.sin(offset + i * i))
  os.system("echo 'could be a database query' >> /dev/null; sleep .1")
  return result

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

Для кода, связанного с процессором, вы должны использовать многопроцессорность

Из статьи: http://www.informit.com/articles/article.aspx?p=1850445&seqNum=9

... многопоточность больше подходит для приложений, связанных с вводом / выводом (ввод / вывод высвобождает GIL, обеспечивая больший параллелизм)...

Подобные ссылки на вопросы о потоках и процессах:
/questions/18917992/mnogoprotsessornost-ili-mnogopotochnost-v-pitone/18918018#18918018
/questions/34569335/obhodnoj-put-python-global-interpreter-lock-gil-v-mnogoyadernyih-sistemah-ispolzuyuschih-nabor-zadach-v-linux/34569347#34569347

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

Это связано с глобальной блокировкой интерпретатора или GIL.

Вот сообщение в блоге об этом, которое включает в себя разговор на эту тему.

Один из способов обойти это ограничение - использовать процессы вместо потоков; это облегчается благодаря многопроцессорному модулю.

Библиотеки Python, написанные на C, могут получить / освободить Global Interpreter Lock (GIL) по желанию. Те, кто не использует объекты Python, могут выпускать GIL, чтобы другие потоки могли заглянуть в него, однако я считаю, что математическая библиотека все время использует объекты Python, так что math.sin эффективно сериализуется. Так как блокировка / разблокировка являются накладными расходами, нет ничего необычного в том, что потоки Python работают медленнее, чем процессы.

Другие вопросы по тегам