Приведи пример когда асинхронность работает медленно
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Ответ: Когда asyncio медленнее, чем синхронный код
Асинхронность не всегда быстрее! Есть несколько сценариев, когда async код может быть медленнее обычного синхронного. Приведу примеры.
Пример 1: Один быстрый I/O запрос
Сценарий
Нужно сделать один API запрос к очень быстрому сервису (локальный, в одной сети).
Синхронный код
import requests
import time
def sync_single_request():
start = time.time()
response = requests.get("http://localhost:8000/api/fast")
return time.time() - start
# Результат: ~0.01 сек
print(f"Sync: {sync_single_request():.4f}s")
Асинхронный код
import aiohttp
import asyncio
import time
async def async_single_request():
start = time.time()
async with aiohttp.ClientSession() as session:
async with session.get("http://localhost:8000/api/fast") as response:
await response.text()
return time.time() - start
start = time.time()
asyncio.run(async_single_request())
print(f"Async: {time.time() - start:.4f}s")
Результаты
Sync: 0.0012 сек ✓ быстрее
Async: 0.0145 сек ✗ медленнее
Async медленнее на 12x!
Почему?
Overhead асинхронности:
asyncio.run(): ~2-3 мс (запуск event loop)
ClientSession создание: ~3-4 мс (инициализация)
Event loop переключения: ~1-2 мс
Сам запрос: ~1 мс
────────────────────────────────
Всего: ~7-10 мс
У синхронного requests просто ~1 мс на запрос
Вывод: если только один быстрый запрос — async медленнее.
Пример 2: Много малых задач
Сценарий
Нужно обработать 100 задач, каждая занимает 1 миллисекунду.
Синхронный код
import time
def process_item(i):
# Быстрая работа
time.sleep(0.001) # 1 мс
return i * 2
def sync_process_many():
start = time.time()
results = [process_item(i) for i in range(100)]
return time.time() - start
print(f"Sync: {sync_process_many():.4f}s") # ~0.1 сек
Асинхронный код
import asyncio
import time
async def async_process_item(i):
await asyncio.sleep(0.001) # 1 мс
return i * 2
async def async_process_many():
start = time.time()
tasks = [async_process_item(i) for i in range(100)]
results = await asyncio.gather(*tasks)
return time.time() - start
start = time.time()
asyncio.run(async_process_many())
print(f"Async: {time.time() - start:.4f}s") # ~0.15 сек
Результаты
Sync: 0.100 сек ✓ быстрее
Async: 0.150 сек ✗ медленнее
Async медленнее на 50%!
Почему?
На малых задачах overhead контекстных переключений больше, чем выигрыш:
Любой await — это переключение контекста (overhead)
Если задача очень маленькая (< 100 мкс):
Overhead переключения > время самой работы
Итого работы медленнее.
Пример 3: CPU-bound вместо async
Сценарий
Сделать вычисления и вернуть результат (CPU-bound, не I/O).
import asyncio
import time
# Синхронный CPU-bound
def cpu_work():
return sum(i*i for i in range(10_000_000))
start = time.time()
result = cpu_work()
print(f"Sync CPU-bound: {time.time() - start:.4f}s") # ~0.5 сек
# Асинхронный CPU-bound (нет смысла)
async def async_cpu_work():
# await не поможет — это синхронное вычисление
return sum(i*i for i in range(10_000_000))
start = time.time()
resyncio.run(async_cpu_work())
print(f"Async CPU-bound: {time.time() - start:.4f}s") # ~0.5 сек
Результаты
Sync CPU-bound: 0.5000 сек
Async CPU-bound: 0.5100 сек (даже немного медленнее из-за overhead)
Вывод
Async не помогает с CPU-bound задачами!
CPU-bound блокирует весь Event Loop:
async def bad_example():
# Пока выполняется sum() — Event Loop заблокирован
# Никакие другие корутины не могут работать
result = sum(i*i for i in range(10_000_000))
return result
async def main():
# Эта корутина будет ждать 0.5 сек
await bad_example()
print("Done") # Напечатается только через 0.5 сек
Пример 4: Неправильное использование asyncio
Плохой паттерн: Sequential вместо parallel
import asyncio
import aiohttp
import time
async def slow_sequential():
start = time.time()
async with aiohttp.ClientSession() as session:
# ❌ Плохо — ждёшь каждый запрос полностью
for i in range(5):
async with session.get(f"https://api.example.com/{i}") as r:
await r.text() # ждём ответ
return time.time() - start
# Результат: 5 сек (если каждый запрос 1 сек)
print(f"Sequential: {asyncio.run(slow_sequential()):.1f}s")
Правильный паттерн: Parallel
async def fast_parallel():
start = time.time()
async with aiohttp.ClientSession() as session:
# ✓ Хорошо — все запросы параллельно
tasks = [
session.get(f"https://api.example.com/{i}")
for i in range(5)
]
responses = await asyncio.gather(*tasks)
for r in responses:
await r.text()
return time.time() - start
# Результат: 1 сек (если каждый запрос 1 сек)
print(f"Parallel: {asyncio.run(fast_parallel()):.1f}s")
Результаты
Sequential: 5.0 сек ✗ медленнее (неправильно)
Parallel: 1.0 сек ✓ быстрее (правильно)
Пример 5: Контекстные переключения в цикле
Проблема
Осталось 4 месяца, но если очень часто yield контроль:
import asyncio
import time
async def many_context_switches():
start = time.time()
for i in range(10000):
# Каждая итерация = контекстное переключение
await asyncio.sleep(0) # empty switch
return time.time() - start
print(f"10k switches: {asyncio.run(many_context_switches()):.4f}s")
# Результат: ~0.15 сек (на самом деле это слишком медленно)
Сравнительная таблица
| Сценарий | Sync | Async | Вывод |
|---|---|---|---|
| 1 быстрый запрос | 0.01s | 0.015s | Sync быстрее |
| 100 I/O запросов в параллель | 100s | 1s | Async быстрее 10x |
| 100 I/O запросов последовательно | 100s | 100s | Одинаково |
| CPU-bound вычисления | 0.5s | 0.5s | Одинаково |
| Смешано: CPU + I/O | Depends | Медленнее всегда | Event Loop заблокирован |
| Очень много малых задач | 0.1s | 0.15s | Sync быстрее |
Когда НЕ использовать async
1. Один-два I/O запроса
# ❌ Не использовать async
async def one_api_call():
...
# ✓ Использовать синхронный requests
response = requests.get(url)
2. CPU-bound задачи
# ❌ Не использовать async
async def cpu_bound():
return sum(i*i for i in range(10_000_000))
# ✓ Использовать multiprocessing
from multiprocessing import Pool
with Pool() as p:
result = p.apply(cpu_bound)
3. Очень много маленьких операций
# ❌ Не использовать async
for i in range(1000):
await asyncio.sleep(0) # не имеет смысла
# ✓ Использовать обычный цикл
for i in range(1000):
process(i)
Правило использования async
Async полезен ТОЛЬКО когда:
1. Есть МНОГО I/O операций (>= 10)
2. I/O операции ДОЛГИЕ (>= 100 мс)
3. Операции могут выполняться ПАРАЛЛЕЛЬНО
4. Вы используете GATHER или GATHER_ALL (не последовательно)
Если нарушить хоть одно условие — async медленнее.
Итог
- Async не магия — это имеет overhead
- Async блокируется на CPU-bound задачах
- Async неправильно используется в последовательных операциях
- На одном-двух быстрых запросах sync быстрее
- Async теряет время на контекстные переключения на малых задачах
Главное правило: использовать async для параллельных I/O операций, не для всего подряд. Инструмент должен соответствовать задаче.