В табл. 59.9 представлены результаты мониторинга набора из N файловых дескрипторов в диапазоне от 0 до N — 1 с помощью вызовов poll(), select() и интерфейса epoll. (Тестирование проводилось в Linux 2.6.25 и было организовано таким образом, что во время каждой операции наблюдения готовым оказывался ровно один файловый дескриптор.) Как видите, с ростом номеров дескрипторов производительность вызовов poll() и select() постепенно снижается. Для сравнения: производительность интерфейса epoll при больших N почти не меняется (небольшое замедление, вероятно, является результатом переполнения кэша процессора в тестовой системе).

Перед тестированием мы изменили значение константы FD_SETSIZE в заголовочных файлах glibc на 16 384, чтобы позволить программе отслеживать большое количество файловых дескрипторов, задействуя вызов select().

Таблица 59.9. Время, которое уходит на 100 000 операций мониторинга с помощью poll(), select() и интерфейса epoll

Количество отслеживаемых дескрипторов (N) — Процессорное время poll() (секунды) — Процессорное время select() (секунды) — Процессорное время epoll (секунды)

10 — 0,61 — 0,73 — 0,41

100 — 2,90 — 3,00 — 0,42

1000 — 35,00–35,00 — 0,53

10 000–990,00 — 930,00 — 0,66

В подразделе 59.2.5 мы узнали, что вызовы select() и poll() демонстрируют низкую производительность при наблюдении за большим количеством файловых дескрипторов. Теперь посмотрим, почему интерфейс epoll не имеет такой проблемы.

• При каждом вызове select() или poll() ядро вынуждено проверять все указанные файловые дескрипторы. Для сравнения: интерфейс epoll позволяет пометить интересующий нас дескриптор с помощью вызова epoll_ctl(). Ядро записывает эту информацию в список, связанный с исходным описанием открытого файла, и затем при каждой операции ввода/вывода, которая делает файловый дескриптор готовым, добавляет элемент в соответствующий список готовности epoll. (Событие ввода/вывода, связанное с одним описанием открытого файла, может сделать готовыми сразу несколько файловых дескрипторов.) Последующий вызов epoll_wait() просто извлекает элементы из списка готовности.

• При каждом вызове select() или poll() передает ядру структуру данных со сведениями обо всех файловых дескрипторах, за которыми нужно наблюдать; затем ядро возвращает эту структуру обратно, предварительно записав в нее информацию обо всех готовых дескрипторах. Для сравнения: в интерфейсе epoll предусмотрен вызов epoll_ctl(), создающий структуру данных с перечнем отслеживаемых файловых дескрипторов прямо в пространстве ядра. После его выполнения последующим вызовам epoll_wait() не нужно передавать ядру информацию о дескрипторах, а в качестве результата мы будем получать список дескрипторов, являющихся готовыми.

Помимо фактов, приведенных выше, стоит отметить еще несколько моментов. Каждый вызов select() требует предварительной инициализации входящей структуры данных, а каждую структуру, возвращаемую вызовами select() и poll() и состоящую из N элементов, следует проверять на наличие готовых файловых дескрипторов. Однако, если верить результатам тестирования, время, которое уходит на эти операции, является несущественным по сравнению с тем, сколько времени тратится на выполнение системных вызовов для мониторинга N дескрипторов. Операции проверки не учитываются в табл. 59.9.

Грубо говоря, производительность вызовов select() и poll() падает линейно по мере увеличивается значения N (количества отслеживаемых файловых дескрипторов). Эта закономерность начинает проявляться при N = 100 и N = 1000 (см. табл. 59.9). К моменту достижения N = 10 000 падение производительности становится даже хуже, чем линейное.

Интерфейс epoll линейно масштабируется в зависимости от количества происходящих событий ввода/вывода, что делает его особенно эффективным в сценариях, характерных для серверов, которые одновременно обслуживают множество клиентов: большинство отслеживаемых файловых дескрипторов не проявляют никакой активности, и лишь немногие из них являются готовыми.

59.4.6. Уведомления, срабатывающие по фронту

Уведомления, предоставляемые интерфейсом epoll, изначально срабатывают по уровню. Это значит, что epoll сообщает нам, будет ли заблокирована операция ввода/вывода для заданного файлового дескриптора. Извещения такого вида предоставляются также вызовами poll() и select().

Перейти на страницу:

Похожие книги