← На главную

io_uring обходит epoll на современных ядрах

20.06.2026 23:07 · hackernews

Автор рассказывает, как дошёл до изучения асинхронного I/O в Linux. Год назад они со студентами написали обратный прокси-сервер TinyGate. Простой, рабочий, но медленный. Студенты требовали сделать что-то полезное, способное тягаться с nginx и haproxy. Пришлось разбираться, как устроены эти инструменты. Первая перепись на epoll дала серьёзный прирост, но до лидеров всё равно не дотянула. В итоге перешли на io_uring — полностью переписали проект с нуля.

Суть в подходах. epoll появился в ядре Linux в 2002 году. Он работает по модели готовности: сообщает, когда I/O возможен, но читать/писать данные всё равно приходится отдельными системными вызовами (read()/write()). Получается два вызова на каждую операцию плюс регистрация через epoll_ctl. Каждый syscall — это переключение контекста между пользовательским режимом и ядром, что даёт огромный оверхед при большом числе соединений.

io_uring появился в 2019 году (ядро 5.1+). Он работает по модели завершения: уведомляет, когда I/O уже сделан. Никакого цикла опроса. Запросы и результаты передаются через кольцевые буферы в общей памяти между приложением и ядром. По умолчанию всё равно нужен вызов io_uring_enter(), но он отправляет сразу пачку операций и забирает пачку результатов — вместо пары syscall на операцию. Если нужно почти полностью избавиться от системных вызовов в стабильном режиме, включается IORING_SETUP_SQPOLL. Он запускает выделенный поток ядра, который сам опрашивает очередь — но ценой нагрузки на CPU.

Автор приводит примеры на C. Для epoll это три syscall: epoll_ctl (регистрация), epoll_wait и read. Для io_uring — создание кольца, отправка запроса и ожидание завершения. Кода меньше, вызовов меньше. Важные нюансы: для настоящего zero-copy нужно предварительно регистрировать буферы через io_uring_register_buffers(), для сети — IORING_OP_SEND_ZC (требует ядра 6.0+). SQPOLL даже при пустой очереди жрёт CPU, хотя есть таймаут бездействия. Ошибки приходят асинхронно в поле res завершённой записи, а не как обычный возврат.

Вывод автора: на современных системах с io_uring тягаться с epoll нет смысла. Для нового проекта на свежем ядре — только io_uring. И он откровенно не понимает, зачем держаться за ядра семилетней давности.

Читать оригинал →