JavaRush /Курси /C# SELF /Проблема продуктивності вводу/виводу (

Проблема продуктивності вводу/виводу ( I/O)

C# SELF
Рівень 41 , Лекція 0
Відкрита

1. Чому ввід/вивід такий повільний?

«Повільність» вводу/виводу (I/O) — одна з вічних тем. Наш код часто «чекає» дані довше, ніж «думає». Розберімося, чому походи до «комори» повільніші порівняно з роботою на «кухні».

Апаратні обмеження (Hardware Limitations)

Жорсткі диски (HDD). Механіка: обертаються пластини, рухається головка. Потрібен час на переміщення (час пошуку) і на оберт — це дає високу затримку.

Твердотільні накопичувачі (SSD). Швидші за HDD, немає механічних частин, але запис і керування зносом комірок роблять операції не миттєвими.

Мережа. Залежить від пропускної здатності та затримок, маршрутизаторів тощо. Навіть за гігабітного каналу відгук до віддаленого сервера — мілісекунди, а не наносекунди, як у CPU.

Накладні витрати операційної системи (OS Overhead)

  • Перевірка прав доступу. Чи має процес право читати/записувати файл?
  • Пошук даних файлу. Файлова система збирає фрагменти.
  • Буферизація та кеш. ОС керує буферами заради ефективності.
  • Перемикання контексту. Поки процес чекає I/O, CPU перемикається — це теж забирає час.

Великий розрив: швидкість CPU vs. швидкість I/O

  • Операція CPU: 0,20,5 наносекунди
  • Читання з RAM: 10100 наносекунд
  • Читання з SSD: 50100 мікросекунд
  • Читання з HDD: 510 мілісекунд
  • Мережевий запит: 10100 мілісекунд і більше

Розрив — колосальний. Якщо «викликати курʼєра» за кожною літерою (I/O), ви друкуватимете повільно, хоч би яким швидким був ваш «друкар» (CPU). Набагато ефективніше брати дані блоками — реченнями та абзацами.

2. Усередині файлу: що відбувається насправді?

Ланцюжок команд під час роботи з файлом виглядає так:

flowchart TD
    A[Ваш код C#] --> B[.NET FileStream]
    B --> C[ОС Windows / Linux / Mac]
    C --> D[Файлова система: NTFS, ext4, APFS]
    D --> E[Драйвер пристрою]
    E --> F[Фізичний диск: HDD / SSD]
  • Ваш код викликає, наприклад, File.ReadAllText(path).
  • .NET внутрішньо використовує FileStream, буфери та системні виклики.
  • ОС керує кешуванням і чергами.
  • Файлова система знаходить блоки даних файлу.
  • Драйвер спілкується з пристроєм.
  • Накопичувач виконує фізичну операцію.

Кожен шар додає накладні витрати. Вузьке місце найчастіше — фізичний носій.

3. Приклад: повільний код на практиці

Антиприклад: читати файл по одному байту через ReadByte().


// ❌ Неефективне читання файлу побайтно
using FileStream fs = new FileStream("bigfile.txt", FileMode.Open);
int currentByte;
while ((currentByte = fs.ReadByte()) != -1)
{
    // Робимо щось із байтом
}

Чому це погано? Кожен виклик ReadByte() — окреме звернення до потоку. На великих файлах таких викликів — мільйони, і система витрачає час на накладні витрати замість корисної роботи.

Правильно — читати блоками:


// ✅ Ефективне читання файлу великими блоками
byte[] buffer = new byte[4096]; // 4 КБ — стандартний розмір буфера
int bytesRead;
using FileStream fs = new FileStream("bigfile.txt", FileMode.Open);
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
    // Обробляємо отриманий блок даних
}

Читання великими блоками дозволяє ОС і диску ефективніше використовувати кеш і черги — час виконання зменшується у рази.

4. Вплив на реальні застосунки

Користувацький інтерфейс (UI). Блокувальний I/O «підвішує» вікно. Важливо виносити операції у фоновий режим або виконувати їх асинхронно й не блокувати головний потік.

Веб-сервери та БД. Сервери постійно читають/записують дані; повільний диск або мережа гальмують увесь сервіс. Буферизація, пул зʼєднань та асинхронний I/O — ключ до пропускної здатності.

Big Data. На гігабайтах/терабайтах будь-яка неефективність масштабується. Розмір блоків, послідовний доступ і потокова обробка визначають результат.

Ігри. Довге завантаження рівнів/ресурсів — це I/O. Правильне пакування ресурсів і читання великими блоками скорочують завантаження.

5. Типові помилки початківців

Поширена помилка — построкове або побайтне читання великих файлів через ReadByte() або занадто маленький буфер (наприклад, 256 байт). Кількість системних викликів зростає, а продуктивність падає.

Є й протилежна крайність: спроба повністю прочитати величезний файл через File.ReadAllBytes — і закономірний OutOfMemoryException. Краще обирати «золоту середину»: розумні блоки (часто 48 КБ або більше, залежно від профілю навантаження) і потокову обробку.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ