Производительность asyncio

Я пытаюсь познакомиться с асинчио, поэтому я решил написать клиент базы данных. Однако производительность точно соответствует синхронному коду. Я уверен, что это мое непонимание концепции. Может кто-нибудь объяснить, что я делаю wriong?

См. пример кода ниже:

class Connection:
    def __init__(self, reader, writer, loop):
        self.futures = deque()

        # ...

        self.reader_task = asyncio.async(self.recv_data(), loop=self.loop)

    @asyncio.coroutine
    def recv_data(self):
        while 1:
            try:
                response = yield from self.reader.readexactly(4)
                size, = struct.unpack('I', response)
                response = yield from self.reader.readexactly(size)

                # ...                

                future = self.futures.popleft()

                if not future.cancelled():
                    future.set_result(response)

            except Exception:
                break

    def send_data(self, data):
        future = asyncio.Future(loop=self.loop)
        self.futures.append(future)

        self.writer.write(data)

        return future


loop = asyncio.get_event_loop()


@asyncio.coroutine
def benchmark():
    connection = yield from create_connection(loop=loop, ...)

    for i in range(10000):
        yield from connection.send_data(...)


s = time.monotonic()

loop.run_until_complete(benchmark())

e = time.monotonic()
print('Requests per second:', int(10000 / (e - s)))

Спасибо заранее.

Ответ 1

Вы допустили ошибку в том, как вы звоните send_data. Сейчас у вас есть это:

@asyncio.coroutine
def benchmark():
    connection = yield from create_connection(loop=loop, ...)

    for i in range(10000):
        yield from connection.send_data(...)

Используя yield from внутри цикла for, вы ожидаете возвращения future из send_data, чтобы получить результат, прежде чем переходить к следующему вызову. Это делает вашу программу в основном синхронной. Вы хотите сделать все свои звонки send_data, а затем ждать результатов:

@asyncio.coroutine
def benchmark():
    connection = yield from create_connection(loop=loop, ...)
    yield from asyncio.wait([connection.send_data(..) for _ in range(10000)])

Ответ 2

Модуль python asyncio одинарный:

Этот модуль предоставляет инфраструктуру для написания однопоточного параллельного кода с использованием сопрограмм, мультиплексирования доступа к вводу/выводу по сокетам и другим ресурсам, запуска сетевых клиентов и серверов и других связанных примитивов.

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

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