CPU-bound та I/O-bound задачі
CPU-bound задача обмежена швидкістю центрального процесора, а I/O-bound задача обмежена часом очікування відповіді від зовнішнього ресурсу. Це не про те, скільки коду написано, а про те, що саме є вузьким місцем: обчислення чи очікування. У JavaScript ця різниця критична, бо виконання коду однопотокове.
Теорія
TL;DR
- CPU-bound: швидкість обмежує процесор. Приклади: великі цикли, криптографія, сортування, шифрування, стиснення.
- I/O-bound: швидкість обмежує ввід/вивід (I/O, Input/Output). Процесор простоює, поки чекає на відповідь. Приклади: запити до БД, HTTP, читання та запис файлів, мережеві виклики.
- CPU-bound код використовує процесор на 100%, I/O-bound майже не використовує його.
- JavaScript однопотоковий, тому CPU-bound операція блокує event loop: UI не оновлюється, події не обробляються, сервер не відповідає на інші запити.
- Лікування CPU-bound: Web Workers, Worker Threads, child_process, розбиття на чанки, WebAssembly або нативні модулі.
- Лікування I/O-bound: асинхронний код, кешування, пул з'єднань, черги.
Швидкий приклад
import fs from 'fs/promises';
// CPU-bound: потік зайнятий обчисленням, event loop стоїть
console.time('cpu');
let total = 0;
for (let i = 0; i < 1e9; i += 1) total += i;
console.timeEnd('cpu');
// I/O-bound: потік вільний, поки операційна система читає файл
console.time('io');
const data = await fs.readFile('large-file.txt', 'utf-8');
console.timeEnd('io');Що саме обмежує швидкість
| Тип задачі | Що обмежує швидкість | Приклади |
|---|---|---|
| CPU-bound | Центральний процесор (CPU), усе впирається в обчислення | Великі цикли, криптографія, сортування, шифрування, стиснення |
| I/O-bound | Ввід/вивід (I/O, Input/Output), процесор простоює, поки чекає на відповідь від зовнішнього ресурсу | Запити до БД, HTTP, читання та запис файлів, мережеві виклики |
CPU-bound, задачі, обмежені процесором
Проблема тут, довгі обчислення, які займають потік цілком.
// Важка CPU-задача: обчислення факторіала
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
console.time('CPU');
console.log(factorial(50000)); // зависне, стек переповниться
console.timeEnd('CPU');Чому це погано в JS (Node.js або браузері): JavaScript однопотоковий, і поки виконується важка операція:
- UI не оновлюється,
- події не обробляються,
- сервер не відповідає на інші запити.
Рішення:
- використати Web Workers (у браузері) або Worker Threads / child_process (у Node.js);
- розбити обчислення на чанки (
setTimeout,setImmediate); - використати WebAssembly або нативні модулі на Rust / C++ для важких розрахунків.
I/O-bound, задачі, обмежені вводом-виводом
Проблема тут не в процесорі, а в часі очікування відгуку зовнішньої системи.
import fs from 'fs/promises';
console.time('IO');
const data = await fs.readFile('largeFile.txt', 'utf-8'); // чекаємо на диск
console.timeEnd('IO');
console.log(data.slice(0, 100));Чому це добре для Node.js: Node.js має event loop і асинхронний I/O, тому поки файл читається або база відповідає, потік вільний і може обслуговувати інші запити.
Порівняння та аналогія
| Параметр | CPU-bound | I/O-bound |
|---|---|---|
| Затримка спричинена | Обчисленнями | Очікуванням зовнішнього ресурсу |
| Використовує CPU | На 100% | Майже не використовує |
| Приклади | Стиснення, шифрування, обробка зображень, ML | Запити до БД, API, читання файлів, мережеві операції |
| Підходить для Node.js | Погано, якщо без воркерів | Відмінно, завдяки async/await та event loop |
| Як пришвидшити | Багатопотоковість, Web Workers, нативні модулі | Асинхронний код, кешування, пул з'єднань |
Проста аналогія:
- CPU-bound: ви самі готуєте складну страву і не можете відволіктися, тобто завантажені на 100%.
- I/O-bound: ви замовили доставку їжі і просто чекаєте, тобто можете зайнятися іншими справами.
Як це впливає на JS і Node.js
| Сценарій | Що відбувається |
|---|---|
| CPU-bound код | Блокує event loop, усі інші операції «висять» |
| I/O-bound код | Node.js ефективно обробляє десятки тисяч запитів паралельно |
Підсумок:
| Тип задачі | Приклади | Оптимізація |
|---|---|---|
| CPU-bound | обчислення, сортування, криптографія, рендеринг | Workers, нативний код, розбиття на частини |
| I/O-bound | HTTP, БД, файли, мережа | async/await, кешування, черги |
Типові помилки
- Вважати, що
async/awaitприскорює обчислення. Асинхронність не додає потоків: важкий цикл, обгорнутий уasync, блокує event loop так само. - Масштабувати CPU-bound сервіс кількістю одночасних запитів. Поки процесор зайнятий одним обчисленням, решта запитів просто стоїть у черзі.
- Виносити у Worker Threads тривіальну роботу. Створення воркера і серіалізація даних коштують дорожче за саме обчислення.
- Плутати «повільно» з «CPU-bound». Повільний запит до БД, це I/O-bound, і воркери його не пришвидшать: тут допоможуть індекси, кеш і пул з'єднань.
- Розбивати обчислення на чанки через
setTimeout(fn, 0)у Node.js, коли доречнішийsetImmediate, і навпаки в браузері забувати проrequestIdleCallback.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.