Ответ 1
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ PEP 0492 определяет только синтаксис и использование для сопрограмм. Они требуют запуска цикла событий, который, скорее всего, asyncio
цикл событий.
Асинхронная карта
Я не знаю никакой реализации map
на основе сопрограмм. Однако тривиально реализовать основные функции map
, используя asyncio.gather()
:
def async_map(coroutine_func, iterable):
loop = asyncio.get_event_loop()
future = asyncio.gather(*(coroutine_func(param) for param in iterable))
return loop.run_until_complete(future)
Эта реализация очень проста. Он создает сопрограмму для каждого элемента в iterable
, объединяет их в одну сопрограмму и выполняет присоединенную сопрограмму в цикле событий.
При условии, что реализация охватывает часть случаев. Однако у него есть проблема. С длинным итерированием вы, вероятно, захотите ограничить количество параллельных параллельных операций. Я не могу придумать простую реализацию, которая эффективна и сохраняет порядок одновременно, поэтому я оставлю это как упражнение для читателя.
Производительность
Вы заявили:
Я считаю, что при выполнении высокопараллельной работы ввода-вывода должно быть меньше накладных расходов.
Это требует доказательств, поэтому приведено сравнение реализации multiprocessing
, gevent
реализации p и моей реализации на основе сопрограмм. Все тесты были выполнены на Python 3.5.
Реализация с использованием multiprocessing
:
from multiprocessing import Pool
import time
def async_map(f, iterable):
with Pool(len(iterable)) as p: # run one process per item to measure overhead only
return p.map(f, iterable)
def func(val):
time.sleep(1)
return val * val
Реализация с использованием gevent
:
import gevent
from gevent.pool import Group
def async_map(f, iterable):
group = Group()
return group.map(f, iterable)
def func(val):
gevent.sleep(1)
return val * val
Реализация с использованием asyncio
:
import asyncio
def async_map(f, iterable):
loop = asyncio.get_event_loop()
future = asyncio.gather(*(f(param) for param in iterable))
return loop.run_until_complete(future)
async def func(val):
await asyncio.sleep(1)
return val * val
Обычно программа тестирования timeit
:
$ python3 -m timeit -s 'from perf.map_mp import async_map, func' -n 1 'async_map(func, list(range(10)))'
Результаты:
-
Итерабельный элемент
10
:-
multiprocessing
- 1,05 сек -
gevent
- 1 с -
asyncio
- 1 с
-
-
Итерируемые элементы
100
:-
multiprocessing
- 1,16 с -
gevent
- 1,01 с -
asyncio
- 1,01 с
-
-
Итерабельный элемент
500
:-
multiprocessing
- 2,31 с -
gevent
- 1,02 с -
asyncio
- 1.03 с
-
-
Итерируемое значение
5000
элементов:-
multiprocessing
- не удалось (нерест 5k-процессов не так хорош!) -
gevent
- 1,12 с -
asyncio
- 1,22 с
-
-
Итерируемый
50000
элементов:-
gevent
- 2,2 с -
asyncio
- 3,25 с
-
Выводы
Concurrency на основе цикла событий работает быстрее, когда программа выполняет в основном I/O, а не вычисления. Имейте в виду, что разница будет меньше, когда будет меньше ввода-вывода, и будут задействованы больше вычислений.
Накладные расходы, создаваемые процессами нереста, значительно больше, чем накладные расходы, введенные на основе событийного цикла concurrency. Это означает, что ваше предположение верно.
Сравнивая asyncio
и gevent
, можно сказать, что asyncio
имеет накладные расходы на 33-45%. Это означает, что создание зеленых деревьев дешевле, чем создание сопрограмм.
В качестве окончательного вывода: gevent
имеет лучшую производительность, но asyncio
является частью стандартной библиотеки. Разница в производительности (абсолютные цифры) не очень значительна. gevent
- довольно зрелая библиотека, а asyncio
относительно новая, но она быстро продвигается.