← Назад к вопросам

В чем отличие I/O-bound и CPU-bound задач?

2.3 Middle🔥 111 комментариев
#Python

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

I/O-bound vs CPU-bound задачи: Различия и оптимизация

Это одна из ключевых концепций, которая определяет оптимизацию кода. Большинство разработчиков это путают.

Основные различия

I/O-bound задачи — время потрачено на ввод-вывод:

  • Чтение с диска (disk I/O)
  • Запросы в БД (network latency)
  • HTTP запросы к API
  • Чтение файлов

CPU-bound задачи — время потрачено на обработку:

  • Математические вычисления
  • Компрессия данных
  • Parsing больших JSON
  • Машинное обучение (ML inference)

Пример: Fetch vs Compute

import time
import requests

# I/O-bound: ждём API
def fetch_sequential():
    start = time.time()
    for i in range(100):
        requests.get('https://api.example.com/data')  # 200ms каждый
    # Total: ~20 seconds (process ждёт)
    return time.time() - start

# CPU-bound: считаем
def compute_expensive():
    start = time.time()
    result = sum(i**2 for i in range(100_000_000))
    # Total: ~5 seconds (CPU работает)
    return time.time() - start

Оптимизация I/O-bound

Threading:

from concurrent.futures import ThreadPoolExecutor

# Sequential: 20 seconds
for url in urls:
    requests.get(url)

# Parallel: 2 seconds (10 threads)
with ThreadPoolExecutor(max_workers=10) as executor:
    results = executor.map(requests.get, urls)

# 10x faster! Потому что thread может работать, пока другой ждёт I/O

Async/await (лучше):

import asyncio
import aiohttp

async def fetch_async():
    async with aiohttp.ClientSession() as session:
        tasks = [session.get(url) for url in urls]
        await asyncio.gather(*tasks)  # ~0.2 seconds

# Async > Threading для I/O:
# - Меньше memory (корутины vs потоки)
# - Нет GIL проблем
# - Быстрее переключение контекста

Connection pooling (для БД):

from sqlalchemy import create_engine

# Без pooling: каждый запрос = новое соединение
engine_bad = create_engine('postgresql://...')

# С pooling: переиспользуй соединения
engine_good = create_engine(
    'postgresql://...',
    pool_size=20,  # Держи 20 соединений
    max_overflow=40
)

# 1000 запросов:
# - Без pooling: 50 seconds
# - С pooling: 5 seconds

Оптимизация CPU-bound

Multiprocessing (обходит GIL):

from multiprocessing import Pool
import os

def expensive_calc(data):
    return sum(i**2 for i in range(10_000_000))

# Threading НЕ поможет (GIL блокирует)
with ThreadPoolExecutor() as e:
    results = list(e.map(expensive_calc, chunks))  # ~20 seconds

# Multiprocessing ПОМОЖЕТ (разные процессы = разные GILs)
with Pool(os.cpu_count()) as pool:
    results = pool.map(expensive_calc, chunks)  # ~2.5 seconds (8 cores)

NumPy Vectorization:

import numpy as np

data = list(range(10_000_000))

# Python loop: 1.5 seconds
result = [x**2 for x in data]

# NumPy: 0.05 seconds
result = np.array(data) ** 2

# 30x faster! NumPy работает с C-уровнем

Numba JIT:

from numba import jit

@jit(nopython=True)
def sum_squares():
    result = 0
    for i in range(100_000_000):
        result += i**2
    return result

# Python: 5 seconds
# Numba: 0.1 seconds (compiled to machine code)

Матрица решений

Тип задачиПроблемаРешениеУскорение
I/O (API)Network waitasync/await10-100x
I/O (БД)Connection setuppooling5-10x
I/O (диск)Disk latencythreading5x
CPUGIL блокируетmultiprocessing2-8x
CPU (циклы)МедленноNumPy10-100x
CPU (вычисления)МедленноNumba10-100x

Как узнать тип задачи

import cProfile

cProfile.run('my_function()')

# Если профайл показывает:
# requests.get, psycopg2, socket.recv → I/O-bound
# Используй: async, threading, pooling

# Если профайл показывает:
# for loops, numpy, calculations → CPU-bound
# Используй: multiprocessing, NumPy, Numba

Реальный пример: ETL Pipeline

class DataPipeline:
    async def run(self):
        # Step 1: Fetch 1000 URLs (I/O-bound)
        # Решение: async → 1 second
        data = await self.fetch_urls_async()
        
        # Step 2: Transform данные (CPU-bound)
        # Решение: multiprocessing → 5 seconds
        transformed = self.transform_parallel(data)
        
        # Step 3: Load в БД (I/O-bound)
        # Решение: connection pooling → 2 seconds
        self.load_batch(transformed)
        
        # Total: 8 seconds
        # vs Sequential: ~50 seconds (6x faster)

Ключевой вывод

I/O-bound: код ждёт внешних систем → используй async/threading

CPU-bound: код считает → используй multiprocessing/NumPy

Выбор неправильного инструмента = 0 улучшения. Выбор правильного = 10-100x ускорение.

В чем отличие I/O-bound и CPU-bound задач? | PrepBro