Почему библиотека asyncio медленнее, чем потоки для этой операции ввода-вывода?

Я пишу программу python, используемую для перечисления имени домена сайта. Например, «a.google.com».

Во-первых, я использовал модуль threading передачи для этого:

 import string import time import socket import threading from threading import Thread from queue import Queue ''' enumerate a site's domain name like this: 1-9 az + .google.com 1.google.com 2.google.com . . 1a.google.com . . zz.google.com ''' start = time.time() def create_host(char): ''' if char is '1-9a-z' create char like'1,2,3,...,zz' ''' for i in char: yield i for i in create_host(char): if len(i)>1: return False for c in char: yield c + i char = string.digits + string.ascii_lowercase site = '.google.com' def getaddr(): while True: url = q.get() try: res = socket.getaddrinfo(url,80) print(url + ":" + res[0][4][0]) except: pass q.task_done() NUM=1000 #thread's num q=Queue() for i in range(NUM): t = Thread(target=getaddr) t.setDaemon(True) t.start() for host in create_host(char): q.put(host+site) q.join() end = time.time() print(end-start) ''' used time: 9.448670148849487 ''' 

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

 import asyncio import string import time start = time.time() def create_host(char): for i in char: yield i for i in create_host(char): if len(i)>1: return False for c in char: yield c + i char = string.digits + string.ascii_lowercase site = '.google.com' @asyncio.coroutine def getaddr(loop, url): try: res = yield from loop.getaddrinfo(url,80) print(url + ':' + res[0][4][0]) except: pass loop = asyncio.get_event_loop() coroutines = asyncio.wait([getaddr(loop, i+site) for i in create_host(char)]) loop.run_until_complete(coroutines) end = time.time() print(end-start) ''' time 120.42313003540039 ''' 

Почему asyncio версия getaddrinfo настолько медленная? Я как-то неправильно использую сопрограммы?

  • Эквивалент asyncio.Queues с рабочими «потоками»
  • «Огонь и забыть» python async / wait
  • @ asyncio.coroutine против async def
  • Python - запуск Autobahn | сервер Python asyncio websocket в отдельном подпроцессе или потоке
  • Запрос на ввод пользователя с использованием экземпляра asyncio.create_server python
  • Передача цикла asyncio по аргументу или использование цикла asyncio по умолчанию
  • Установка дескриптора в python3.5 асинхронно
  • Asyncio.gather vs asyncio.wait
  • One Solution collect form web for “Почему библиотека asyncio медленнее, чем потоки для этой операции ввода-вывода?”

    Во-первых, я не могу воспроизвести разницу в производительности почти такую ​​же, как та, что вы видите на моей машине Linux. Я последовательно вижу около 20-25 секунд для версии с резьбой и между 24-34 секундами для версии asyncio .

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

    Во-вторых, и, что еще более важно, asyncio версия getaddrinfo на самом деле просто вызывает socket.getaddrinfo в ThreadPoolExecutor :

     def getaddrinfo(self, host, port, *, family=0, type=0, proto=0, flags=0): if self._debug: return self.run_in_executor(None, self._getaddrinfo_debug, host, port, family, type, proto, flags) else: return self.run_in_executor(None, socket.getaddrinfo, host, port, family, type, proto, flags) 

    Для этого используется стандартный ThreadPoolExecutor по умолчанию, который имеет только пять потоков :

     # Argument for default thread pool executor creation. _MAX_WORKERS = 5 

    Это не так много параллелизма, который вы хотите для этого прецедента. Чтобы сделать его более похожим на threading версию, вам нужно использовать ThreadPoolExecutor с 1000 потоками, установив его как исполнителя по умолчанию через loop.set_default_executor :

     loop = asyncio.get_event_loop() loop.set_default_executor(ThreadPoolExecutor(1000)) coroutines = asyncio.wait([getaddr(loop, i+site) for i in create_host(char)]) loop.run_until_complete(coroutines) 

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

    Наконец, в каждом примере у вас на самом деле нет эквивалентного кода – в threading версии используется пул работников, которые используют очередь queue.Queue , в то время asyncio версия asyncio порождает сопрограмму для каждого отдельного элемента в списке URL- asyncio . Если я сделаю версию asyncio для использования asyncio.Queue и пула сопрограмм, в дополнение к удалению операторов печати и созданию большего стандартного исполнителя, я получаю практически идентичную производительность с обеих версий. Вот новый код asyncio :

     import asyncio import string import time from concurrent.futures import ThreadPoolExecutor start = time.time() def create_host(char): for i in char: yield i for i in create_host(char): if len(i)>1: return False for c in char: yield c + i char = string.digits + string.ascii_lowercase site = '.google.com' @asyncio.coroutine def getaddr(loop, q): while True: url = yield from q.get() if not url: break try: res = yield from loop.getaddrinfo(url,80) except: pass @asyncio.coroutine def load_q(loop, q): for host in create_host(char): yield from q.put(host+site) for _ in range(NUM): yield from q.put(None) NUM = 1000 q = asyncio.Queue() loop = asyncio.get_event_loop() loop.set_default_executor(ThreadPoolExecutor(NUM)) coros = [asyncio.async(getaddr(loop, q)) for i in range(NUM)] loop.run_until_complete(load_q(loop, q)) loop.run_until_complete(asyncio.wait(coros)) end = time.time() print(end-start) 

    И выход каждого из них:

     dan@dandesk:~$ python3 threaded_example.py 20.409344911575317 dan@dandesk:~$ python3 asyncio_example.py 20.39924192428589 

    Обратите внимание, что есть некоторая изменчивость из-за сети. Оба они будут иногда на несколько секунд медленнее, чем это.

    Python - лучший язык программирования в мире.