Страничный кэш кэширует недавно открытые страницы с жесткого диска, тем самым сокращая время поиска для последующих обращений к тем же данным. Страничный кэш не повышает производительность при первом доступе к странице с жесткого диска. Таким образом, если приложение собирается прочитать файл один раз и только один раз, то лучше обойти кэш страницы. Это возможно с помощью флага O_DIRECT. Это означает, что ядро не учитывает эти конкретные данные для кэша страниц. Уменьшение конкуренции за кэш означает, что другие страницы (к которым будут обращаться многократно) имеют больше шансов остаться в кэше страниц. Это улучшает коэффициент попадания в кэш, обеспечивая более высокую производительность.
void ioReadOnceFile()
{
/* Использование direct_fd и direct_f обходит кэш страниц ядра.
* - direct_fd — низкоуровневый файловый дескриптор
* - direct_f - это файловый поток, похожий на тот, который возвращает fopen()
* ПРИМЕЧАНИЕ: Используйте getpagesize() для определения буферов оптимального размера.
*/
int direct_fd = open("имя файла", O_DIRECT | O_RDWR);
FILE *direct_f = fdopen(direct_fd, "w+");
/* прямой дисковый ввод-вывод выполняется ЗДЕСЬ*/
fclose(f);
close(fd);
}
Рассмотрим случай чтения в большом файле (т.е. базе данных), состоящем из огромного количества страниц. Каждая последующая страница, к которой вы обращаетесь, попадает в кэш страниц только для того, чтобы быть выброшенной позже, по мере прочтения все большего и большего количества страниц. Это значительно снижает коэффициент попадания в кэш. В этом случае страничный кэш не обеспечивает никакого прироста производительности. Следовательно, при доступе к большим файлам лучше обойти страничный кэш.
void ioLargeFile()
{
/* Использование direct_fd и direct_f обходит кэш страниц ядра.
* - direct_fd — низкоуровневый файловый дескриптор
* - direct_f - это файловый поток, похожий на тот, который возвращает fopen()
* ПРИМЕЧАНИЕ: Используйте getpagesize() для определения буферов оптимального размера.
*/
int direct_fd = open("largefile.bin", O_DIRECT | O_RDWR | O_LARGEFILE);
FILE *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, ядро помещает в очередь операцию записи для данных, помещая процесс приложения в заблокированный ввод-вывод. К сожалению, это означает, что процесс текущего приложения заблокирован и не может выполнять какую-либо другую обработку (или ввод-вывод, если уж на то пошло) до завершения этой операции записи.
Когда приложение инициирует асинхронное чтение ввода-вывода, функция read() обычно возвращается после чтения подмножества большого блока данных. Приложению необходимо многократно вызывать read() с размером данных, оставшихся для чтения, до тех пор, пока все необходимые данные не будут считаны. Каждый дополнительный вызов read приводит к некоторым накладным расходам, так как он вводит переключение контекста между пользовательским пространством и ядром. Реализация замкнутого цикла для многократного вызова read() приводит к потере циклов процессора, которые могли бы быть использованы другими процессами. Следовательно, обычно блокировка реализуется с помощью select() до тех пор, пока следующий read() не вернет ненулевые байты считывания. т.е. ASYNC блокируется так же, как и SYNC read.
Когда приложение инициирует асинхронную запись ввода-вывода, ядро обновляет соответствующие страницы в кэше страниц и помечает их как «грязные». Затем элемент управления быстро возвращается в приложение, которое может продолжить работу. Данные сбрасываются на жесткий диск позже, в более оптимальное время (при низкой нагрузке на ЦП) более оптимальным способом (последовательная групповая запись).
Следовательно, SYNC-чтение и ASYNC-запись , как правило, являются хорошим вариантом, поскольку они позволяют ядру оптимизировать порядок и время базовых запросов ввода-вывода.