Буферизация, кэш и модели ввода-вывода

Почему программа не ждёт диск на каждый байт и как ОС прячет медлительность устройств.

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

Зачем буферизация

Диск медленный, а каждый системный вызов дорог. Если писать файл по байту, это тысячи дорогих обращений к диску. Решение — буфер: данные копятся в памяти и сбрасываются на диск разом, когда буфер заполнится. Один большой write вместо тысячи маленьких — огромная экономия.

Та же идея при чтении: ОС читает с диска не запрошенный байт, а целый блок «с запасом», рассчитывая на пространственную локальность. Следующие чтения соседних байтов попадут уже в буфер.

Дисковый кэш (page cache)

ОС держит недавно использованные блоки диска в оперативной памяти — это дисковый кэш. Если программа просит блок, который уже в кэше, диск не трогают вовсе — отдают из памяти. Поэтому второе чтение того же файла обычно мгновенно. Свободную RAM ОС охотно занимает под кэш — отсюда «вся память занята» в мониторе, хотя это полезная занятость.

cache = {}            # блок -> данные в памяти
disk_reads = 0

def read_block(num):
    global disk_reads
    if num in cache:
        return "из кэша (быстро)"
    disk_reads += 1
    cache[num] = f"данные блока {num}"
    return "с диска (медленно)"

requests = [10, 11, 10, 12, 11, 10]
for r in requests:
    print(f"Запрос блока {r}: {read_block(r)}")

print(f"Запросов всего: {len(requests)}")
print(f"Реальных чтений с диска: {disk_reads}")
print(f"Сэкономлено обращений к диску: {len(requests) - disk_reads}")

Вывод:

Запрос блока 10: с диска (медленно)
Запрос блока 11: с диска (медленно)
Запрос блока 10: из кэша (быстро)
Запрос блока 12: с диска (медленно)
Запрос блока 11: из кэша (быстро)
Запрос блока 10: из кэша (быстро)
Запросов всего: 6
Реальных чтений с диска: 3
Сэкономлено обращений к диску: 3

Цена кэша: данные могут потеряться

У буферизации и кэша есть оборотная сторона: если данные ещё в буфере и не записаны на диск, а тут отключили питание — они пропадут. Поэтому есть операция flush/sync — принудительный сброс буфера на диск. Базы данных делают sync в нужные моменты, чтобы гарантировать сохранность.

Модели ввода-вывода

Когда программа запрашивает ввод-вывод (например, читает из сети или с диска), у неё есть выбор, как ждать результата.

Блокирующий ввод-вывод

Программа вызывает read и ждёт, пока данные не придут. Поток «замирает» — ничего другого не делает. Просто в написании, но неэффективно, если ждать приходится долго (например, ответа по сети).

Неблокирующий ввод-вывод

Вызов сразу возвращает управление: «данных пока нет, зайди позже». Программа может заняться чем-то ещё и периодически проверять готовность. Сложнее, но поток не простаивает.

Асинхронный ввод-вывод

Программа говорит «начни читать и сообщи, когда будет готово», и продолжает работу. Когда данные придут, ОС уведомит (через callback или событие). Так работают высоконагруженные серверы, обслуживающие тысячи соединений одним потоком.

МодельПоток ждёт?Сложность
Блокирующийда, замираетпростая
Неблокирующийнет, опрашиваетсредняя
Асинхронныйнет, уведомятвысокая

Полезные команды

free -h        # память: сколько занято кэшем (buff/cache)
sync           # сбросить все буферы на диск
iostat         # статистика ввода-вывода по устройствам

Итог

  • Буферизация копит данные в памяти, чтобы обмениваться с диском крупными порциями.
  • Дисковый кэш хранит недавние блоки в RAM — повторное чтение идёт без диска.
  • Кэш рискует потерей данных при сбое питания; для надёжности есть flush/sync.
  • Блокирующий ввод-вывод прост, но поток ждёт; неблокирующий и асинхронный не простаивают.
  • Асинхронный ввод-вывод позволяет одному потоку обслуживать тысячи соединений.
Проверьте себя
1. Зачем нужна буферизация при записи на диск?
AЧтобы шифровать данные
BЧтобы накопить данные в памяти и записать их крупной порцией вместо множества мелких
CЧтобы освободить оперативную память
DЧтобы замедлить запись и не перегружать диск
2. Что произойдёт при повторном чтении блока, который уже в дисковом кэше?
AБлок перечитается с диска заново
BБлок вернётся из памяти без обращения к диску
CВозникнет page fault
DБлок будет удалён
3. Чем асинхронный ввод-вывод отличается от блокирующего?
AАсинхронный всегда медленнее
BПри асинхронном поток не ждёт — он продолжает работу, а ОС уведомит о готовности
CБлокирующий не использует системные вызовы
DАсинхронный работает только с сетью
Поддержать проект