Программист — это звучит гордо
Поговорим сегодня о планировщиках ввода/вывода (I/O Scheduler) в Linux, какие они бывают, чем отличаются и как их можно поменять. С практической точки зрения тема может быть интересной для оптимизации работы системы с SSD.
Теоретическая часть
Хотя я и упоминал о SSD, но что бы глубже понять тему прийдётся начинать с HDD. Их главная проблема долгое время доступа к произвольному месту на диске. Оно складывается из времени позиционирования головки на дорожке и время ожидания поворота диска на угол необходимый для доступа к сектору. Для понимания масштабов бедствия — переключение на другую дорожку в пределах одного цилиндра занимает порядка 1 миллисекунды, переход на соседний цилиндр 2-4 ms. Время оборота диска (7200 rpm) — составляет 8 ms. Для современных дисков время произвольного доступа колеблется от 2.5 до 16 ms — это время, за которое винчестер гарантированно выполнит операцию чтения или записи на любом участке магнитного диска. С проблемой активно борются производители жёстких дисков, например одна из применяемых оптимизаций — упреждающее чтение, в расчёте на то, что следующая команда чтения будет из этой же области диска.
Следующий нюанс — архитектура программ. Чтение обычно синхронная операция, пока мы не получили данные, продолжать работу мы не можем. А вот запись напротив чаще всего асинхронно и приложению не так важно когда реально будут записаны эти данные. Поэтому желательно, что бы запросы на чтение выполнялись как можно быстрее, а вот запись можно отложить на какое-то время.
Теперь имея представление о проблемах, рассмотрим, как планировщики ввода/вывода их решают. В современных дистрибутивах обычно из коробки присутствуют 4 планировщика:
Noop I/O Scheduler
Самый простой и требующий меньше всего ресурсов. Запросы выстраиваются в FIFO (First In, First Out) очередь и никак не сортируются, из оптимизаций применяются только операции слияния, когда это возможно.
Deadline I/O Scheduler
Каждому входящему запросу назначается предельное время (deadline) через сколько он должен быть выполнен. Для чтения это по умолчанию 500 ms, для записи 5 сек. Все новые запросы пишутся в одну из двух очередей — FIFO очередь на чтение и FIFO очередь на запись. Таким образом в голове каждого из списков храниться запрос, у которого deadline ближе всего. Так же запрос вставляется ещё и в общую очередь, отсортированную так, что бы минимизировать кол-во перемещений головки диска (фактически по номеру блока).
При записи на диск, планировщик проверяет первые две очереди, если есть запросы с истёкшим deadline — выполняет их в первую очередь. В противном случае берёт запросы из общей, отсортированной, очереди.
Anticipatory I/O Scheduler
Упреждающий планировщик. Он пытается решить некоторые проблемы предыдущего. Как я уже говорил, приложения обычно читают данные с диска в синхронном режиме — прочитали кусок из файла и только потом запрашивают следующий. Это приводит к тому, что после выполнения первого запроса на чтение, планировщик переключается к выполнению других запросов и головка диска «уходит» в другое место. А приложение шлёт следующий запрос, который с большой долей вероятности расположен рядом с предыдущим. Если бы планировщик подождал немного, он бы смог быстро обслужить и этот запрос.
Вот такое ожидание этот планировщик и добавляет. Он после каждого запроса на чтение, 6 ms ждёт следующего запроса от приложения, если ничего не получил, продолжает работать как deadline scheduler.
Complete Fair Queuing (CFQ) I/O Scheduler
Полностью справедливая очередь. Для каждого процесса заводится своя собственная очередь. Очередь стандартно сортируется и дополнительно назначается более высокий приоритет синхронизированным запросам (типично чтение). Планировщик по очереди обходит очереди и в течении кванта времени выполняет запросы из этой очереди. Как только время вышло или очередь завершилась — переходит к следующей.
Выбор планировщика
Нет единого ответа на вопрос какой из планировщиков лучше. Каждый из них имеет сильные и слабые стороны и поэтому нужно смотреть на характер нагрузки, отношение операций записи к чтению и т.п. Разве что noop scheduler будет для HDD гарантированно плохим выбором.
Вот допустим Red Hat в своей статье называет лучшим CFQ и приводит такой график:
А IBM с этим мнением не согласена и считает, что Deadline лучше других:
Всё меняется, когда приходит SSD, у которого время произвольного доступа не зависит от расположения блока. И поэтому сложная сортировка запросов на чтение только снижает производительность системы, и впустую тратит ресурсы процессора и памяти. У SSD есть проблемы с записью и для их решения возможно стоило бы поискать специфичный планировщик, но на мой вкус соотношение риска от нестандартного планировщика к пользе пока ещё слишком высоко. А значит следует выбирать noop scheduler, как самый простой и быстрый. Хотя опять же, если у вас специфические нагрузки, то могут быть варианты.
Практическая часть
С теорией разобрались, переходим к практике. Стоит предупредить, что я проверял инструкцию ниже только на Arch Linux. И хотя я не использовал ничего специфичного, но гарантировать работу на всех дистрибутивах не могу, проверяйте сначала на виртуальной машине.
Посмотреть список поддерживаемых планировщиков, например для sda:
$ cat /sys/block/sda/queue/scheduler noop deadline [cfq]
Тот, что в квадратных скобках (cfq) — используемый сейчас. У меня sda — SSD диск, поэтому поменяем scheduler на noop. Сделать это можно на лету, без перезагрузки:
$ sudo tee /sys/block/sda/queue/scheduler$ cat /sys/block/sda/queue/scheduler [noop] deadline cfqСтоит помнить, что выбранный планировщик вступит в действие не сразу, а через некоторое время. И работать настройка будет до перезагрузки. Что бы установить другой умолчательный планировщик для всех дисков, добавте параметр ядра "elevator=scheduler_name".
Покажу на примере GRUB и планировщика "deadline". Откройте "/etc/default/grub" и добавьте в "GRUB_CMDLINE_LINUX_DEFAULT" нужный параметр, у меня получилось вот так:
. GRUB_CMDLINE_LINUX_DEFAULT="quiet elevator=deadline"sudo grub-mkconfig -o /boot/grub/grub.cfgИ перезагружаемся. Способ рабочий, но у меня в системе используются и SSD и HDD диски и хочется использовать для них разные планировщики. Проще всего это сделать через механизм "systemd-tmpfiles", который, в том числе, используется для записи значений при старте системы. Создайте файл "/etc/tmpfiles.d/10_scheduler.conf" вот с таким содержимым:
w /sys/block/sda/queue/scheduler - - - - noopТеперь после перезагрузки sda будет иметь планировщик noop, а все остальные - дефолтный. Как вариант можно обойтись без systemd и использовать udev. Тем более это входит в его зону ответственности. Создаём "/etc/udev/rules.d/60-schedulers.rules":
ACTION=="add|change", KERNEL=="sda", ATTR=="0", ATTR="noop"Приоритет 60 в имени файла я использовал, т.к. где-то вычитал и поверил автору, что это наиболее безопасная позиция для подобного рода настроек. После перезагрузки получаем результат аналогичный предыдущему: у sda - noop scheduler, у остальных умолчательный.
Тестирование
Сложная тема, для любого планировщика можно подобрать такой набор тестов, который покажет что именно он самый быстрый. Один лучше обслужит одно приложение читающее большие файлы, другой покажет свою мощь на множестве мелких параллельных чтений и т.д. Параметров который влияют на производительность неприлично много.
И если вы например проверите скорость копирования файлов, то результат покажет лишь как быстро с данным планировщиком копируются файлы. Если это у вас основной паттерн использования диска - то это адекватный тест, в противном случае он ни о чём.
Могу порекомендовать статью на тему, на мой взгляд очень обстоятельную. Там же вы найдёте методику тестирования при помощи утилиты fio. Пересказывать не буду, поскольку в статье и так всё достаточно хорошо описано.