Подтвердить что ты не робот

В Python существует ли асинхронный эквивалент многопроцессорной или параллельной .futures?

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

Несомненно, что-то подобное уже существует, будь то в стандартной библиотеке или в широко распространенном пакете?

4b9b3361

Ответ 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 относительно новая, но она быстро продвигается.

Ответ 2

Вы можете использовать greenlets (легкие потоки, в основном сопрограммы) для этого, или несколько более высокий уровень gevent lib, построенный поверх них:

(из docs)

import gevent
from gevent import getcurrent
from gevent.pool import Group

group = Group()

def hello_from(n):
    print('Size of group %s' % len(group))
    print('Hello from Greenlet %s' % id(getcurrent()))

group.map(hello_from, xrange(3))

def intensive(n):
    gevent.sleep(3 - n)
    return 'task', n

print('Ordered')

ogroup = Group()
for i in ogroup.imap(intensive, xrange(3)):
    print(i)

print('Unordered')

igroup = Group()
for i in igroup.imap_unordered(intensive, xrange(3)):
    print(i)

Выход:

Size of group 3
Hello from Greenlet 31904464
Size of group 3
Hello from Greenlet 31904944
Size of group 3
Hello from Greenlet 31905904
Ordered
('task', 0)
('task', 1)
('task', 2)
Unordered
('task', 2)
('task', 1)
('task', 0)

Стандартные ограничения использования lightweight-vs-proper-multicore применяются к greenlets vs threads. То есть они параллельны, но не обязательно параллельны.

Быстрое редактирование для людей, которые видят это в будущем, так как Ярослав проделал большую работу по определению различий между Python asyncio и gevent:

Почему gevent over async/ждет? (все они очень субъективны, но применимы ко мне в прошлом)
- Не переносимый/легко доступный (не только 2.X, но 3.5 принес новые ключевые слова)
- асинхронный и ожидающий имеет тенденцию распространять и заражать кодовые базы - когда кто-то еще инкапсулировал это для вас, это супер пупер с точки зрения разработки и удобочитаемости/ремонтопригодности
- В дополнение к выше, я (лично) чувствую, что высокоуровневый интерфейс gevent очень "питоничен".
- Меньше веревки, чтобы повесить себя. В простых примерах два кажутся похожими, но чем больше вы хотите делать асинхронные вызовы, тем больше шансов, что вам нужно испортить что-то основное и создать условия гонки, блокировки, неожиданные действия. Не нужно изобретать петлю imho.
- Производительность Gevent оценивает прошлые тривиальные примеры и используется и тестируется в больших производственных средах. Если вы не знаете много о асинхронном программировании, это хорошее место для начала.

Почему асинчо, а не Gevent?
- Если вы можете гарантировать версию Python и не иметь доступа к сторонним пакетам/пунктам, это дает вам возможность без проблем.
- Как и в предыдущем случае, если вы не хотите быть привязанным к проекту, который был медленным, чтобы принять Py3k, прокат собственного небольшого набора инструментов является хорошим вариантом.
- Если вы хотите настроить мелодию, вы отвечаете!