Комментарии 36
На мой взгляд, это нормальная ситуация в больших opensource-проектах — да, есть части кода, которые не очень нравятся основателю. Тем не менее, они удовлетворяют нужды пользователей, не ломают другие части проекта и работают так, как задумано.
Это было, понятно, в те времена, когда основной системой на серверах был именно Solaris, а не Linux… и теперь это выкинуть невозможно, так как совместимость же!
А пост — перевод.
Так накрепко зависает, что минус-девять не помогает.
Мы придумали несколько решений и костылей, aio было в числе кандидатов.
Точно не забыли опции soft, intr при монтировании?
Если предполагается, что с файлами на запись будет работать только ваша собственная программа, и возможность ошибки на каждой стадии будет предусмотрена — soft вполне себе допустимый вариант.
В случае же read-only сетевая nfs-шара в режиме soft mount и вовсе ничем принципиально не отличается от, к примеру, обычного cd-rom.
The intr / nointr mount option is deprecated after kernel 2.6.25.
Небольшое описание git.kernel.dk/cgit/linux-block/commit/?h=for-next&id=8923ebc04818fcb506829591aa8704baefd661ec
Вот они io_submit.c io_setup.c итд.
Похоже, только автор исходной статьи вроде ей не пользовался.
П.С. В дебиане указан другой источник https://pagure.io/libaio
Создал IOCP, привязал сокет к нему, засабмитил несколько WSARecv(), причём можно подряд на один сокет — они будут отрабатываться в порядке поступления.
При отправке — никакого ожидания. Дальше слушаешь поступление сообщений в IOCP, по адресу OVERLAPPED опознаёшь выполненный запрос, разбираешь ответ.
Абсолютно честный проактор без странностей типа «ну мы вроде отправляем асинхронно, но на самом деле — вы тут и заблокируетесь». Ядро умеет опознавать такие заранее подсунутые буфера и складывать в них данные без копирования через промежуточный буфер — ещё с тех времён, когда большинство юниксов про zero-copy только мечтали.
Есть, конечно, проблемы и тут. Перемещать хэндлы между IOCP для баланса нагрузки — есть только недокументированный путь. Сокеты не во всех ожиданиях сочетаются с другими файлоподобными объектами. Нельзя заранее узнать или регулировать количество доступных активных асинхронных запросов. Но если с ними справиться — система работает на максимуме эффективности без лишних потерь.
Что Линусу не нравится в проакторной асинхронности — понять невозможно, а описать внятно без всех этих crap/horrible/etc. он не в состоянии. Когда-то он точно так же ругался на kqueue про «silly triplet», в результате принял в ядро то же самое, но кривее и несовместимо, под названием epoll. Здесь он не хочет ни один вариант AIO впускать полноценно (чтобы оно не блокировалось); боится плодить ядерные нити и FSM на сокеты? Честно непонятно.
Люди, а за что минусуете? Человек дело говорит. Меня тоже всегда удивляло, что такое сильное ядро не умеет полноценно асинхронно работать, тогда как есть пример как это нормально реализовано в другом ядре.
IOCP легко эмулируется через epoll(), есть статья сравнения на хабре habr.com/ru/company/infopulse/blog/415403
т.е. достаточно написать свой небольшой враппер — и удобство точно такое же.
По скорости медленнее тоже врядли будет, в свое время я перепробовал целую кучу методов, включая IOCP, TransmitFile, WSAEnumNetworkEvents… В простом кейсе легкий запрос-ответ, на винде было примерно 60к rps против 110k rps на лине на той же машине.
Я даже пробовал недокументированные извращения с Nt* функциями, но результата это не дало.
А вот основная проблема, что open() и CreateFile() обращаются к метаданным на винте и тоже могут повиснуть — так и остается проблемой, приходится использовать пул потоков.
Спасибо за разъяснения. А почему тогда такие простые, судя по вашим словам, обёртки недоступны по умолчанию в линуксе? Вот вывод логов через rsyslog из контейнеров блокируется? Потому что мы в ЦИАН уже получали проблемы от этого пару лет назад. Возможно, что-то изменилось или мы что-то не так готовили, но при резком увеличении логов у нас питон глох, если logstash не успевал принимать логи с rsyslog машины.
Ну и про rps — асинхронность она же не про rps, а про масштабирование io heavy приложений (современные микросервисы) и их использование cpu на это дело.
Это то да, но там, насколько я помню, было то, что даже отправка в syslog из приложения (чтобы на том конце уже дропнули в режиме overflow) было блокирующей операцией и это деградировало отработку запросов в приложении. И это мы говорим про udp! Блокирующая отправка по udp, Карл! Вот это меня удивляет.
rsyslog честно не знаю как он оптимизирован, но я вижу проект не такой простой, там и шифрование есть github.com/rsyslog/rsyslog/tree/master/runtime
Я бы первым делом посмотрел strace (или htop + s на треде) и perf питоновского процесса на отсылке и сервера что шлет в сеть. Узкое место наверняка найдется.
Так же вижу вот такие баг репорты bugzilla.redhat.com/show_bug.cgi?id=1047039
там в комментах видно использование select(), что уже недопустимо. Вполне возможно все решается конфигурацией.
При попытке выстроить подобную архитектуру с единственным epoll instance придется использовать mutex. Это убивает всякое планирование потоков (и оно вовсе не LIFO, которое Windows IOCP как бы обещает где-то в документации) и серьезно просаживает производительность. Поэтому с epoll поступают иначе — создают несколько epoll instances (например, по кол-ву ядер CPU) и с каждым epoll instance работает только один поток без каких-либо mutex.
Вот только наличие нескольких epoll instance уже требует распределения (а распределение может требовать и учета — зависит от алгоритма) сокетов по этим нескольким epoll instance. Неудачное распределение может привести к перекосам в нагрузке потоков (Why does one NGINX worker take all the load?) — когда одни потоки перегружены, а другие простаивают (в то время как в других epoll instance есть необработанные события).
Насчет эмуляции реактора в IOCP (для тех, кто не хочет выделать буфер заранее) — тот же Boost.Asio реализвал это поверх IOCP через null_buffers (Reactor-Style Operations).
Похоже, нужно почитать про epoll — даже что-то вводное типа Вся правда о linux epoll уже говорит о том, что, возможно, с многопоточностью (pre-fork) все работает, если готовить определенным образом. Я не уверен, что такой рецепт подходит для всех — вроде как есть причины, по которым та же Asio все еще не использует edge triggered режим.
Вообще на версию ядра в RHEL смотреть не стоит. Нужно смотреть на подсистемы. Я поддерживаю драйвер для наших железок, так иногда они просто целые подсистемы бекпортируют (сталкивался, когда меняли подсистемы и мы завязались на проверку по версии ядра: если старое, использовать старый подход, если новое — новый и обломались, когда стали собирать на обновлённой CentOS...). Так что вполне может статься, что EPOLLEXCLUSIVE там благополучно присутствует после некого увеличения внутреннего номера ядра.
Равномерно на полную или равномерно по чуть-чуть? С точки зрения затрат процессора на переключение, вымывание кэшей и т.п. — второе сильно невыгодно. Тут LIFO в IOCP сделано изначально умно.
Сравнение — есть. Но рассказа про «лёгкость эмуляции» — нет, вы что-то совсем не то прочитали.
Лёгкости эмуляции не может быть уже потому, что epoll сам по себе в принципе не может поддерживать проактивный заказ операции, он работает только в реактивном режиме. Для сокетов, пайпов и т.д. — такой режим годится: включили «мы ждём каких угодно данных» и получаем. Но уже заказать чтение файла с конкретной позиции — невозможно: там операция на момент заказа должна быть специфицирована с указанием позиции и размера. Select, poll на файлах или устройствах прямого доступа не работает (select всегда возвращает готовность, хотя это кривое легаси: по-нормальному он должен был бы отшибать с EINVAL). Потому и были придуманы aio_read с компанией.
Это было про чисто факт возможности операции. Второе — эффективность. Если вы проактивно заказали чтение порции байт из сети в пользовательский буфер, ядро может выбрать, как ему эффективнее и насколько оно вообще умеет и умеет железо:
1) прочитать из буфера сетевухи в сокетный буфер в ядре, потом копировать в пользовательский буфер — два копирования;
2) скопировать из буфера сетевухи в пользовательский буфер напрямую — одно копирование;
3) скомандовать сетевухе «читать TCP в этот буфер» и задать пользовательский буфер — ноль копирований.
С epoll и аналогами у вас только (1). С IOCP можно даже потребовать выставить сокетный буфер в ноль байт и всё общение производить с пользовательскими буферами.
То же самое для отправки в сеть. Меньше тупых копирований — меньше загрузка процессора, меньше ограничений в скорости.
> В простом кейсе легкий запрос-ответ, на винде было примерно 60к rps против 110k rps на лине на той же машине.
Лёгкий запрос-ответ — да, верю — тут цена сисколла уже может влиять, на Linux она очень жёстко минимизируется, на Windows — есть, пишут, неустранимые препятствия. А на плотном одностороннем или двустороннем потоке (предположим потоковое прокси, которое релеит через себя)?
> А вот основная проблема, что open() и CreateFile() обращаются к метаданным на винте и тоже могут повиснуть — так и остается проблемой, приходится использовать пул потоков.
Эта проблема везде. Squid использовал для задачи удаления пул unlinkd-процессов :)
io_submit: альтернатива epoll, о которой вы никогда не слышали