При сравнении этих тестов следует отметить, что асинхронная версия, ну, в общем, асинхронная: asyncio тратит значительные усилия, чтобы гарантировать, что сопрограммы, которые вы отправляете, могут работать одновременно. В вашем конкретном случае они не на самом деле работают одновременно, потому что process_usage
ничего не ждет, но система на самом деле этого не делает. Синхронная версия, с другой стороны, не дает таких положений: она просто запускает все последовательно, следуя счастливому пути переводчика.
Более разумное сравнение было бы для синхронной версии, чтобы попытаться распараллелить вещи способом, идиоматическим для синхронного кода: используя потоки. Конечно, вы не сможете создать отдельный поток для каждого process_usage
, потому что, в отличие от asyncio со своими задачами, ОС не позволит вам создать миллион потоков. Но вы можете создать пул потоков и кормить его задачами:
def main():
with concurrent.futures.ThreadPoolExecutor() as executor:
for key in range(0,1000000):
executor.submit(process_usage, key)
# at the end of "with" the executor automatically
# waits for all futures to finish
В моей системе это занимает ~ 17 с, в то время как версия asyncio - ~ 18 с. (Более быстрая версия asyncio занимает ~ 13 с.)
Если прирост скорости asyncio настолько мал, можно спросить, зачем беспокоиться об asyncio? Разница в том, что с помощью asyncio, принимая идиоматический код и сопрограммы, связанные с вводом-выводом, вы получаете практически неограниченное количество задач, которые в реальном смысле выполняются одновременно. Вы можете создавать десятки тысяч асинхронных соединений одновременно, и asyncio с радостью выполнит их одновременную манипуляцию, используя высококачественный поллер и масштабируемый планировщик сопрограмм. В случае пула потоков число задач, выполняемых параллельно, всегда ограничено количеством потоков в пуле, обычно не более сотен.
Даже примеры игрушек имеют значение для обучения, если ничего больше. Если вы используете такие микробенчмарки для принятия решений, я советую приложить еще больше усилий, чтобы примеры были более реалистичными. Сопрограмма в примере asyncio должна содержать хотя бы один await
, а в примере синхронизации должны использоваться потоки для эмуляции того же уровня параллелизма, который вы получаете с помощью async. Если вы настроите оба варианта так, чтобы они соответствовали вашему фактическому варианту использования, тогда эталонный тест фактически даст вам возможность принять (более) обоснованное решение.