-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Работа с сетью #3
Comments
yndx-antoshkka 22 марта 2017, 11:30 Вы можете помочь в развитии, если попробуете текущий прототип https://github.com/chriskohlhoff/networking-ts-impl и скажете, чего вам в нём не хватает, какие ошибки нашли. h4tred 27 марта 2017, 12:35 yndx-antoshkka 27 марта 2017, 15:55 Синхронный интерфейс в n4626.pdf так же присутствует. h4tred 28 марта 2017, 8:00
Posix AIO на том же Linux реализован на User-level в виде пула потоков (про Linux AIO/libaio слышал, но там свои нюансы). Не знаю как покажет себя на больших нагрузках. На macOS вроде только для файлов, не для сокетов. Можно делать эмуляцию поверх epoll, не сложно, но тоже снижает эффективность: вытянутые native_handle() из сокетов Asio, подсунутые в libev + синхронный интерфейс (с nowait) показали куда лучший результат, чем так же логика, полностью реализованная средствами Asio (асинхронный интерфейс). Тестировалось на небольшом HTTP 1.1 сервере. Ещё смутило обилие аллокаций памяти: они случаются на каждый асинхронный вызов (про свои аллокаторы знаю, но это другой вопрос). В любом случае, AIO не используется в Asio, используется дополнительный слой абстракции поверх существующих механизмов. Если грубо: асинхронный интерфейс Asio нативно ложится на Windows IOCP, но требует дополнительных затрат ресурсов и менее эффективен поверх epoll/kqueue и иже с ними. Потом и ляпунул про "прогиб". Хотя, ЕМНИП, Крис изначально Asio начинал на Windows писать, это может несколько объяснить выбор интерфейса библиотеки... Как минимум, в данной схеме трудно реализовать подход: дождаться события на сокете, пусть будет чтения, запустить обработчик, узнать количество данных (не портируемо, но kqueue эту информацию может передать вместе с эвентом, в Linux - дополнительный сискол), после чего выделить блок памяти нужного размера и за один присест прочитать все данные. Или выделить дополнительный блок памяти и прочитать при помощи readv (не портируемо, но оптимально). Или переиспользовать текущие свободные буфера. В Windows IOCP и Asio ты передаёшь блок памяти сразу в обработчик и если данных больше, чем блок, нужно снова запускать асинхронную операцию. В контексте Asio это ещё и аллокация памяти на вызов. В kqueue ещё можно и чтенеие не делать, если есть ошибка и/или данных ноль - эта информация тоже есть в эвенте. Из всего этого плюс ещё немного хочется асбтракции над сокетами, но оторванными от механизма асинхронности, как минимум - меньше кода библиотеки, легче понять, разобраться, быстрее собираться. А средства асинхронной работы с сетью - отдельно и что бы была возможность выбрать оптимальный паттерн. PS в этом отношении интерфейс Java NIO мне больше нравится. maksimus1210 3 апреля 2017, 16:45 yndx-antoshkka 4 апреля 2017, 15:10
Мне кажется мы расходимся в терминологии. Давайте разберем на примере: у вас 100 сокетов, и сразу произошло 16 событий. Как вы предлагаете их обрабатывать в вашей схеме? h4tred 5 апреля 2017, 3:46
Небольшое допущение: сервер однопоточный и используем epoll.
Собственно первый пункт - это и есть "дождаться события на сокете". В случае Asio, если заглянуть во внутрь, различия начинаются с 3го пункта: Так как буфер мы должны передать до возникновения события, мы реально не можем узнать сколько данных в буфере ядра, и не можем выполнить:
и прочитать данные за один системный вызов read/readv. Если мы не угадали с размером буфера, нам придётся делать ещё одну операцию асинхронного чтения, как я писал выше - плюс аллокация памяти. Плюс, при таком подходе трудно реализовать EPOLLET, что помогает, в том числе, снизить количество вызовов epoll_wait() в итоге. Если в текущей схеме мы применим EPOLLET, не дочитаем данные и снова запросим асинхронную операцию - для этого сокета мы уже колбека не дождёмся. Ну и слово последовательно в пункте 2) я тоже не просто так выделил. Дабы не возникло ощущения, что в Asio заполнение буффера данными будет происходить на уровне ядра и одновременно (или почти). Это верно (насколько хватает моих знаний) только для Windows. В любом случае, в однопоточном сервере, колбеки будут вызываться последовательно. h4tred 5 апреля 2017, 4:01 yndx-antoshkka 5 апреля 2017, 12:55
Это очень необычные требования, переводящие ваше серверное приложение в разряд экзотических. Обычно серверные приложения многопоточны и тогда проактор - идеальная архитектура, которая даёт возможность наиболее быстро, с минимальными задержками и равномерной нагрузкой обрабатывать запросы. Типичный пример - http сервер. Вы как разработчик заинтересованы в том, чтобы как можно скорее обработать запросы с как можно меньшими задержками. Когда пользователь запрашивает страницу, браузер может открыть несколько соединений и запрашивать разные ресурсы - поэтому ситуация, когда у вас сразу 16 событий случается на сервере - норма. Если вы будете обрабатывать каждое событие по пол секунды последовательно, как в акторе - пользователь прождёт 8 секунд и будет недоволен. Если примените проактор и обработаете в 16 потоков - пользователь прождёт пол секунды. НО при всё при этом, реализовать на ASIO предложенную вами схему можно, и достаточно просто:
Есть даже пример в оффициальной документации: http://www.boost.org/doc/libs/1_63_0/doc/html/boost_asio.html#boost_asio.overview.core.reactor P.S.: Подобный подход c null_buffers() (но многопоточный) я давным давно использовал для асинхронной работы с PostgreSQL: оправлял запрос, получал нативный сокет соединённый с базой данных, ждал когда появится нужноe количество байт ответа (асинхронно читал в null_buffers()) , после чего из сокета читал через API Postgres. h4tred 7 апреля 2017, 8:33
Это допущение. Всё это примерно одинаково делается и в несколько потоков (обычно по числу ядер). Зачем усложнять объяснение?
Так, опять про разные вещи говорим. Проактор в однопоточном режиме будет так же выполнять запросы последовательно. Ровно как актор вполне может их обслуживать в несколько потоков. Повторюсь, в Asio на Linux используется epoll, который актор по своей природе.
А вот этот момент я как-то упустил. Спасибо. Нужно будет погонять на пробном сервере (http, http_parser от nginx). На данный момент по числу RPS такой расклад был (сервер - многопоточный):
Понятно, что выкладки выше - вода без конкретных цифирь, но сейчас их найти не могу (точнее остались несуразные выкладки для разных условий тестирования). Плюс запускалось на localhost, а не в реальной сети. Попробую перепроверить в ближайшее время, тем более, что повод появился:
Так что пока у меня остаётся только претензия на аллокации памяти при каждом асинхронном запросе. yndx-antoshkka 7 апреля 2017, 13:33
Скорее всего у вас есть класс, который отвечает за работу с установленным соединением и содержит сокет. Добавьте в этот класс небольшой массив slab и перенаправляйте все аллокации в этот класс, чтобы он аллоцировал из slab если может. Держите пул инстансов, чтобы устанавливать соединения быстрее. При таком подходе производительность возрастёт где-то на порядок или на два. P.S.: будет очень круто, если кто-то напишет статью на Хабр о том, как выкрутить производительность ASIO на максимум. Если понадобится помощь - пишите. Marat Abrarov 27 февраля 2019, 18:47
Есть Million RPS Battle и есть идеи того, что в теории можно рекомендовать при использовании Asio на *nix и на Windows (например, как соотносится Why does one NGINX worker take all the load? с Asio). К сожалению, нет железа (включая быструю сеть), где можно было бы проверить, что из теории действительно работает, а что можно опустить в угоду более простому коду. Понятно, что "заточенное" решение на том же C & epoll "уделает" Asio, отягощенную некоторыми абстракциями и необходимостью обеспечивать гарантии, которые эти самые абстракции предполагают (для удобства пользователей Asio – за удобство рано или поздно приходится платить). Непонятно, насколько большой overhead дает Asio (по сравнению с C & epoll) – стоит ли он этих самых удобств (относительная кроссплатформенность)? Насколько я помню список рассылки Asio и release notes – кажется, что автор пытался улучшить производительность Asio на Linux и уменьшил кол-во мест, где используются блокировки. Рецепт io_service with BOOST_ASIO_CONCURRENCY_HINT_UNSAFE_IO + io_service instance per worker thread должен работать. Где бы проверить… yndx-antoshkka 27 февраля 2019, 20:06
Там, как и в подавляющем количестве примеров в интеренете, не оптимальная работа с ASIO: в этот класс надо добавить slab аллокатор и использовать его в асинхронных методах; надо нормально настраивать сокеты (например добавить no_delay(true) при первом получении сокета), да и плюс там UB при использовании acceptor. Marat Abrarov 13 марта 2019, 2:56
Во-первых, непонятно, как это поможет производительности на практике (тот же Yandex использует что-то вроде tcmalloc и ему хватает производительности... хотя там это вызвано характером нагрузки и trade-off между сложностью и теоретической эффективностью). Во-вторых, есть virtan/mrps#3 (что там не так с acceptor? можно и это поправить) и мои локальные тесты не показывают выигрыша больше погрешности, когда используется Asio custom memory allocation (в собственным ma_echo_server я реализовал еще и параллельные чтение и запись, но и это не помогло в моих локальных тестах). Думаю, можно еще преаалоцировать сокеты для входящих соединений, хотя я уже выключил mutex на io_service (за счет concurrency hint), который используется для per-io_service-socket-implementation-registry, так что overhead от Asio уже сведен почти на нет. Насчет TCP_NODELAY на стороне сервера - эта опция включена на стороне тестового клиента (один для всех тестов и написан тоже на Asio), а на стороне сервера, даже в конкурирующих решениях эта опция вроде как не включается (явно). Значит, "нечестно" включать TCP_NODELAY и в сервере на Asio (c++-virtan) yndx-antoshkka 13 марта 2019, 11:16
На старом glibc это ускоряло сервер приблизительно в три раза.
Супер! Что с производительностью, по сравнению с epoll / libev ? Marat Abrarov 13 марта 2019, 13:44
Вот тут (см. комментарии) пишут о "в 2,5 раза" при Ubuntu-11.04/AMD Phenom 9650/4096Gb/gcc-4.5.2/boost-1.47.0 и "при полностью съеденных 4ех ядрах"
Еще не дошел до такого сравнения, но тот же c++-virtan, что до pull request, что после показывает приблизительно одинаковые результаты. Разница в пределах погрешности. Полагаю, что мои условия тестирования - VMware Workstation на Windows 10 + Docker + запуск и клиента, и сервера на одном и том же железе - не позволяют "раскрыться" тестируемому образцу. P.S. Что-то не приходят уведомления, хотя я подписан на комментарии. Не сигнал ли это о том, что пора перевести данную дискуссию в другой формат и на другую площадку (GitHub, email)? Хотя бы до появления результатов ("кто-то напишет статью на Хабр о том, как выкрутить производительность ASIO на максимум") Hare76 22 ноября 2017, 22:41 Ну а что касается того момента, что в asio идет "прогиб" под IOCP - не соглашусь. Используя IOCP можно написать код практически идентичный в своей парадигме epoll. Сам подход у них очень идентичен по своей сути. Но вот если к IOCP начинать лепить дополнительно асинхронное winapi - вот здесь уже и получается каша, которая не нужна в стандарте. |
Перенос предложения: голоса +58, -1
Автор идеи: maksimus1210
Любое современное ПО требует работы с сетью, а отсутствие в стандартной библиотеке таких классов заставляет искать альтернативы и переход на другой язык программирования, в языке GO очень развитая библиотека для работы с сетью и если требуется реализовать HTTP сервер, то я зык GO идеально подходит для этого.
The text was updated successfully, but these errors were encountered: