Кеш сторінок кешує нещодавно отримані сторінки з жорсткого диска, таким чином скорочуючи час пошуку для наступних доступів до тих самих даних. Кеш сторінок не покращує продуктивність при першому зверненні до сторінки з жорсткого диска. Отже, якщо програма збирається прочитати файл один раз і лише один раз, то обхід кешу сторінок є кращим способом. Це можливо за допомогою прапора O_DIRECT. Це означає, що ядро не враховує ці конкретні дані для page-кешу. Зменшення суперечок щодо кешу означає, що інші сторінки (до яких можна звертатися неодноразово) мають більше шансів залишитися в кеші сторінок. Це покращує співвідношення звернень до кешу, забезпечуючи кращу продуктивність.
void ioReadOnceFile()
{
/* Використання direct_fd та direct_f обходить кеш сторінок ядра.
* - direct_fd — низькорівневий дескриптор
файлу* - direct_f — це файловий потік, подібний до того, що повертає fopen()
* ПРИМІТКА: Використовуйте getpagesize() для визначення буферів оптимального розміру.
*/
int direct_fd = open("ім'я файлу", O_DIRECT | O_RDWR);
ФАЙЛ *direct_f = fdopen(direct_fd, "w+");
/* прямий дисковий ввід/вивід зроблено ТУТ*/
fclose(f);
close(fd);
}
Розглянемо випадок читання у великому файлі (тобто базі даних), що складається з величезної кількості сторінок. Кожна наступна доступна сторінка потрапляє в кеш сторінок, а потім відпадає, оскільки читається все більше і більше сторінок. Це сильно знижує співвідношення звернень до кешу. У цьому випадку кеш сторінок не забезпечує жодного приросту продуктивності. Отже, було б краще обійти page-cache при доступі до великих файлів.
void ioLargeFile()
{
/* Використання direct_fd та direct_f обходить кеш сторінок ядра.
* - direct_fd — низькорівневий дескриптор
файлу* - direct_f — це файловий потік, подібний до того, що повертає fopen()
* ПРИМІТКА: Використовуйте getpagesize() для визначення буферів оптимального розміру.
*/
int direct_fd = open("largefile.bin", O_DIRECT | O_RDWR | O_LARGEFILE);
ФАЙЛ *direct_f = fdopen(direct_fd, "w+");
/* прямий дисковий ввід/вивід зроблено ТУТ*/
fclose(f);
close(fd);
}
Планувальник вводу-виводу оптимізує порядок операцій введення-виведення, які повинні бути поставлені в чергу на жорсткий диск. Оскільки час пошуку є найсерйознішим штрафом на жорсткому диску, більшість планувальників введення-виведення намагаються мінімізувати час пошуку. Це реалізовано як варіант алгоритму ліфта, тобто переупорядкування випадково впорядкованих запитів від численних процесів до порядку, в якому дані присутні на жорсткому диску, вимагає значної кількості процесорного часу.
Деякі завдання, які включають складні операції, як правило, обмежені тим, наскільки швидко центральний процесор може обробляти величезні обсяги даних. Складний планувальник вводу/виводу, що працює у фоновому режимі, може споживати дорогоцінні цикли процесора, тим самим знижуючи продуктивність системи. У цьому випадку перехід на простіший алгоритм на кшталт no-op зменшує навантаження на процесор і може покращити продуктивність системи.
Echo noop > /sys/block//queue/scheduler
Хоча це в кінцевому підсумку допоможе виконати роботу, це точно не найоптимальніший спосіб. З точки зору ядра, найоптимальнішим розміром для запитів введення-виведення є розмір блоку файлової системи (тобто розмір сторінки). Оскільки всі введення-виведення у файловій системі (і кеші сторінок ядра) є предметом сторінок, має сенс для програми також виконувати передачу в розмірах, кратних розміру сторінки. Крім того, з огляду на те, що зараз багатосегментні кеш-пам'яті проникають на жорсткі диски, можна було б отримати величезну вигоду, виконуючи введення-виведення в кількості блоків, кратних розміру.
Для визначення оптимальної статистики розміру
блоку можна використовувати наступну команду:--printf="bs=%s optimal-bs=%S\n" --file-system /dev/
Коли програма ініціює зчитування вводу/виводу SYNC, ядро ставить у чергу операцію зчитування даних і повертається лише після того, як весь блок запитуваних даних буде прочитаний назад. Протягом цього періоду Ядро позначить процес програми як заблокований для введення-виведення. Інші процеси можуть використовувати центральний процесор, що призводить до загальної кращої продуктивності системи.
Коли програма ініціює запис вводу/виводу SYNC, ядро ставить у чергу операцію запису даних, що переводить процес програми в заблокований ввід/вивід. На жаль, це означає, що поточний процес програми заблоковано і він не може виконувати будь-яку іншу обробку (або введення-виведення, якщо на те пішло), доки ця операція запису не завершиться.
Коли програма ініціює читання ASYNC вводу/виводу, функція read() зазвичай повертається після зчитування підмножини великого блоку даних. Додаток повинен багаторазово викликати read() з розміром даних, що залишаються для зчитання, доки всі необхідні дані не будуть зчитані. Кожен додатковий виклик для читання вносить певні накладні витрати, оскільки вводить перемикання контексту між простором користувача та ядром. Реалізація жорсткого циклу для багаторазового виклику read() витрачає цикли процесора, які могли б використовувати інші процеси. Отже, зазвичай здійснюється блокування за допомогою select(), доки наступний read() не поверне читання з ненульовими байтами. тобто ASYNC створено для блокування так само, як це робить читання SYNC.
Коли програма ініціює запис вводу-виводу ASYNC, ядро оновлює відповідні сторінки в кеші сторінок і позначає їх забрудненими. Потім елемент керування швидко повертається до програми, яка може продовжувати роботу. Дані скидаються на жорсткий диск пізніше в більш оптимальний час (низьке навантаження на процесор) більш оптимальним способом (послідовні групові записи).
Отже, SYNC-читання та ASYNC-запис, як правило, є хорошим способом, оскільки вони дозволяють ядру оптимізувати порядок і час виконання базових запитів введення-виведення.