Pull to refresh

Comments 37

Рассматривали Haskell вместе с/вместо Go?

"Сложность простоты"

https://habr.com/ru/post/469441/

TL;DR поста "Сложность простоты": автору (как он сам указывает, C# (mostly) developer; хотя он Rust, Scala знает) больше понравился Haskell.

Нет, не рассматривал, да и не уверен что Haskell удобен в контексте системного программирования.

Ну, к примеру, я попробовал по быстрому нагуглить как в Haskell сделать кастомный syscall. И с ходу не получилось найти. Конечно я тут не претендую на объективность, но кажется что язык и комьюнити не совсем об этом.

Ну я все таки рассуждал в контексте линукса. А так why not.

UFO just landed and posted this here

Не могу говорить за Haskell. Но, например, в GO FFI(cgo) имеет свою, довольно ощутимую цену на вызов С функции из GO (в десятки раз медленнее). И если мы рассматривает io_uring в контексте высокого throughput то таких вещей хочется по максимуму избегать.

UFO just landed and posted this here

Черт, это я совсем не на ту статью, куда хотел, комментарий написал. Надо было сначала проснуться. Прошу прощения.

Тоже думал написать серию статей по io_uring, но с переходом на новую работу руки так и не дошли.

Очень рад был увидеть такую статью. Технология очень интересная.

Спасибо! Да технология действительно интересная, если судить по issues на гитхабе построить асинхронный бекенд с iouring пытаются и для питона и для шарпов и для go и вообще для всего что движется :) Правда все на стадии прототипов.

Я сейчас прикручиваю io_uring к Envoy, и мне казалось, что в случае нулевых "params" никаких воркеров в ядре не запускается. Они запускаются, если указать флаг IORING_SETUP_SQPOLL (попробуйте свой бенчмарк с ним). Без него основной выигрыш в производительности происходит за счёт экономии на syscall'ах - меньше нужно переключений контекста, если сразу несколько системных операций (accept, writev, readv, connect, close) положить в буфер и один раз позвать io_uring_submit().

Не совсем так, пул воркеров запускается в любом случае (кому то же нужно выполнять сисколы). Грепните вот так:

ps auxf | grep io_wqe_

По поводу IORING_SETUP_SQPOLL - эта опция поднимает еще один дополнительный тред на одно кольцо. Его задача разгребать SQ, освобождая нас от ручного вызова io_uring_enter для подтверждения новых SQE (ну или submit в случае liburing). Но вообще эту тему (и более подробные бенчмарки) я как раз собираюсь оставить для будущих публикаций. Там можно много и по разному конфигурировать:

  • один io_uring

  • много io_uring

  • один ui_uring с IORING_SETUP_SQPOLL

  • много io_uring с IORING_SETUP_SQPOLL

  • много io_uring но на ограниченном пуле воркеров

  • и т.д.

У меня греп ничего не показывает. Правда, и с IORING_SETUP_SQPOLL новых тредов в ps auxf тоже невидно. Возможно, у меня ps или ядро какие-то неправильные.

Я, грешным делом, думал, что сисколы выполняются в том же треде, что и приложение, но с переключением в контекст ядра. То есть контроль исполнения передаётся ядру на время выполнения сискола, а приложение стоит и ждёт возврата. С IORING_SETUP_SQPOLL нет нужды в сисколах - IO операции выполняются ядром асинхронно, по моим предположениям, в polling-треде. В моих нагрузочных тестах видно как с IORING_SETUP_SQPOLL один CPU core полностью нагружается ядром, а CPU core на который запинено приложение тоже полностью нагружено, но практически не переключается в контекст ядра. В общем, было бы здорово развеять мои заблуждения в новый статьях.

Вот ещё вопрос возник. Если IO операции выполняются отдельными ядерными тредами, они попадут в одну cgroup с приложением или нет? Если нет, то, наверно, в k8s может случиться проблема "noisy neighbor".

Ну собственно поэтому этот момент в статье описан как туманный :) Например вот в недавних версиях появилась возможность IORING_REGISTER_IOWQ_MAX_WORKERS - ограничить размер пула. Но что это за пул и как работает - в доке одни намеки, приходится копать самому.

По поводу cgroup в целом ничего не могу сказать. Но в 5.12+ появилась такая фича:

       IORING_FEAT_NATIVE_WORKERS
              If  this  flag  is set, io_uring is using native workers for its async helpers.  Previous kernels used kernel threads that assumed the identity of the original io_uring owning task, but
              later kernels will actively create what looks more like regular process threads instead. Available since kernel 5.12.

возможно она решит проблему (если таковая имеется)?

Тоже не вижу потоков (io_wqe или чего-то похожее), только [kworker/...]. Ядро 5.15 от Ubuntu. Не знаете почему?

Возможно Вам будем интересно, я еще раз провел ресерч работы io_uring именно в контексте операций на сокетах которые могут быть не блокируемые (иначе говоря при актуальной FEAT_FAST_POLL). Вот небольшое обсуждение по этому поводу.

Почитал. Впечатлился.


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


В чём заключается разница:


  1. Блокирующие вызовы невозможно отменить. Вызов будет висеть в очереди, пока не придут/отправятся данные, либо не случится ошибка. В случае же неблокирующего вызова можно отписаться от нотификации готовности к чтению/записи в любой момент. Отсюда следует, что использовать io_uring в качестве бэкэнда для планировщика в C# без костылей не получится, т.к. CancellationToken попросту не будет работать.


  2. В Linux нет асинхронной работы с дисками. Существующие решения работают с блокирующими вызовами в пуле потоков. Очевидно, что такие решения масштабируется довольно плохо. Технология io_uring же решает эту проблему.


Круто что впечатлились! По первому пункту, возможно я Вас не понял, но io_uring умеет отменять операции (даже если они уже засабмиченны из SQ в ядро) - для этого есть спец операция IORING_OP_ASYNC_CANCEL.

Спасибо, тогда это решает все возможные проблемы. Просто с документацией по этой технологии пока всё плохо, и я тупо не смог найти эту информацию, т.к. функционал IORING_OP_ASYNC_CANCEL был добавлен в ядро сильно позже, в версии 5.5.

По поводу документации так и есть. В данный момент лучшая дока по операциям - тесты liburing.

объясните человеку не в теме - как обрабатывается переполнение буферов?

О каких именно буферах речь?

Очень просто:


Если у вас накопилось больше запросов, чем влазит в буфер, то вы должны ждать, пока ядро их прочитает, и только потом дописывать новые. Если вы не используете poll-режим, тогда просто достаточно вызывать io_uring_submit после заполнения буфера.


Если же у ядра накопилось ответов больше, чем влазит в буфер, то оно заполнит столько элементов, сколько влезет в буфер, и будет ждать, пока клиент не вычитает ответы. Если вы не используете poll-режим, тогда буфер заполнится при следующем вызове io_uring_wait_*.

Спасибо! добавлю что для в случае переполнения CQ поведение зависит от версии ядра, раньше лишние cqe дропались (надо проверять FEAT_NODROP вообщем)

а) не перезаписать свой (еще) необработанный запрос

в случае использования liburing не получится перезаписать, если работать с SQ руками то можно

Если у вас накопилось больше запросов, чем влазит в буфер, то вы должны ждать, пока ядро их прочитает, и только потом дописывать новые

По какому признаку пользователь должен это отслеживать?

Если же у ядра накопилось ответов больше, чем влазит в буфер, то оно заполнит столько элементов, сколько влезет в буфер, и будет ждать, пока клиент не вычитает ответы.

По какому признаку ядро отслеживает, какие пакеты вычитаны и могут быть безопасно перезаписаны?

По какому признаку пользователь должен это отслеживать?

В случае liburing - функция io_uring_get_sqe вернет null. Если пишите либу сами и работаете с чистыми сисколами то у Вас есть доступ к head и tail SQ + размер SQ тоже известен, так что просто сами смотрите

По какому признаку ядро отслеживает, какие пакеты вычитаны и могут быть безопасно перезаписаны?

В случае liburing - опять же мы подтверждаем прием cqe вызовом функции, под капотом - двигаем head CQ буфера (это значение шарится между юзер спейсом и ядром)

В случае liburing — опять же мы подтверждаем прием cqe вызовом функции

Мне, кстати, логика работы функции io_uring_cqe_seen совершенно не нравится, т.к. делает она вовсе не то, что декларируется в её названии. Она двигает счётчик на 1, но никакой логики по отношению к cqe к ней нет:


Например, мы можем считать сразу несколько cqe через io_uring_peek_batch_cqe, а затем начать обрабатывать их не по порядку, тогда вызов io_uring_cqe_seen может привести к тому, что первые cqe могут быть затёрты.

Мне, кстати, логика работы функции io_uring_cqe_seen совершенно не нравится

согласен с Вами, думаю это сделано из-за красивого api

Например, мы можем считать сразу несколько cqe через io_uring_peek_batch_cqe, а затем начать обрабатывать их не по порядку, тогда вызов io_uring_cqe_seen может привести к тому, что первые cqe могут быть затёрты.

я даже об это слегка спотыкался, правда теперь, немного разобравшись, везде где батчим cqe (а батчим везде где нужен высокий throughput) использую io_uring_cq_advance

Кольцевой буфер со всеми указателями (head, tail) шарится между ядром и приложением, есть общий алгоритм работы с ним, который описан в статье.

Проверил, что будет, если создать uring на 32 элемента, и стал заполнять SQ много тысяч раз без вычитывания CQ. Kworker не заблочился, в файл всё записалось. Kernel 5.15.

О кольцевых. Ring buffer имеет конечную емкость.

Я, может быть, коряво выразился. Попробую переформулировать: за какими признаками должен следить пользователь io_uring чтобы

а) не перезаписать свой (еще) необработанный запрос

б) с гарантией успевать обрабатывать все ответы ядра.

Конкретные примеры и истории косяков приветствуются.

б) с гарантией успевать обрабатывать все ответы ядра.

Мне кажется, ответа тут нет. У меня ядро 5.15, и в нём никакой проблемы с обработкой нет. Я запихивал в uring миллион различных таймеров, и весь этот миллион корректно складировался в ядре без потери ответов.


Но судя по документации, sumbit может вернуть -EBUSY. В этом случае нужно приостановить отправку запросов в ядро и вычитать результаты. Но я с таким не сталкивался.

Sign up to leave a comment.

Articles