A> while(true)
A> {
A> const int packet_awailable = get_pending_count(sock);
A> for (int i = 0; i < packet_awailable; ++i)
A> {
A> int nRead = recvfrom(sock, packet, PACKET_LEN, 0, NULL, NULL);
A> _ASSERT(nRead == PACKET_LEN);
A> stat.OnPacket(packet);
A> }
A> Sleep(20);
A> }
Идея со Sleep(20) — так себе.
Поток не пробуждается сразу же по приходу хотя бы одного пакета.
Представьте, что пакет приходит в первую мс слипа, а мы все равно спим еще 19 мс.
Лучше для ожидания поступления данных использовать select.
Вторая проблема, если за время слипа придет слишком много пакетов, то возможна их потеря.
Т.к. у сокета внутренний буфер конечного размера.
Здравствуйте, qaz77, Вы писали:
Q>Идея со Sleep(20) — так себе. Q>Поток не пробуждается сразу же по приходу хотя бы одного пакета. Q>Представьте, что пакет приходит в первую мс слипа, а мы все равно спим еще 19 мс. Q>Лучше для ожидания поступления данных использовать select.
такая актуальность и не требуется, обработка всех собранных за "такт" пакетов происходит пачкой, поток не прыгает в сон и не пробуждается без надобности
в случае обработки по приходу поток бы постоянно дергался (пакеты идут 20 К в секунду) полюсов не видно.
Q>Вторая проблема, если за время слипа придет слишком много пакетов, то возможна их потеря. Q>Т.к. у сокета внутренний буфер конечного размера.
конечно, но с существенным запасом,
при потоке 20К в секунду за 20 мс придет 400 пакетов, а буфер у меня на ~4000
Здравствуйте, tryAnother, Вы писали: A>такая актуальность и не требуется, обработка всех собранных за "такт" пакетов происходит пачкой, поток не прыгает в сон и не пробуждается без надобности A>в случае обработки по приходу поток бы постоянно дергался (пакеты идут 20 К в секунду) полюсов не видно.
Если много датаграм приходит в единицу времени, то ожидание по select не приведет к лишним пробуждениям потока.
Когда пробудились кушаем все, пока очередь пакетов не пустая.
На практике надо замерять производительность со Sleep и select.
Все-таки смущает хардкод константы 20 мс.
Допустим софт будет крутится на слабенькой машине и время манипуляции с принятым пакетом будет сильно больше...
Если это не массовый софт, а написанный для конкретной железки, то наверное и так норм.
Я вот пишу коробочный и даже сейчас в 2022 еще пентиум 3 у клиентов бывает.
Здравствуйте, tryAnother, Вы писали:
A> ++seq_; A> if(seq_ != seq) A> printf("Sequence broken, recv %d expect %d, dif %d\n", seq, seq_, seq-seq_);
блэт, слона то я и не заметил, это UDP, алло, откуда здесь гарантия последовательности пакетов? и откуда гарантия последовательного вызова хендлеров в азио?
УК>надо сделать один раз, например в конструкторе, хендлер один и тот же, зачем он аллоцируется каждый раз?
допускаю что тут лишние аллокации, которых можно избежать, но 20К аллокаций в секунду не большая нагрузка
и синхронная версия по потерям ведет себя аналогично асинхронной, то есть дело не в этом.
а можно ли посмотреть на код где "асио не напрягаясь может хендлить на одном ядре на порядок больше пакетов" и интересно какой там pps и общая скорость.
УК>блэт, слона то я и не заметил, это UDP, алло, откуда здесь гарантия последовательности пакетов? и откуда гарантия последовательного вызова хендлеров в азио?
гарантий никто и не обещал, это понятно, но сеть там точка точка, никого кроме железки и принимающей машины нет, и она работает хорошо, пакетам там теряться некуда.
вопрос в сравнении подходов к чтению:
— синхронный и асинхроный asio теряют пачки по единицам — десяткам пакетов от единиц до десятков в час в зависимости от загрузки машины,
— подход с чтением через sleep теряет единицы пакетов в сутки
Здравствуйте, tryAnother, Вы писали:
A>гарантий никто и не обещал, это понятно, но сеть там точка точка, никого кроме железки и принимающей машины нет, и она работает хорошо, пакетам там теряться некуда.
рация работает на бронепоезде? речь не о потерях, а о порядке пакетов
A>вопрос в сравнении подходов к чтению: A>- синхронный и асинхроный asio теряют пачки по единицам — десяткам пакетов от единиц до десятков в час в зависимости от загрузки машины, A>- подход с чтением через sleep теряет единицы пакетов в сутки
ещё раз, текущий тест "потери пакетов" невалиден, инкремент счётчика тут не канает, если надо посчитать количество принятых пакетов — заведи vector<bool> например на миллион элементов, и засовывай ему по индексу, который равен id пришедшего пакета true, после отправки миллиона пакетов выждать паузу и посчитать, сколько в векторе true значений, если меньше миллиона, то есть потери.
Здравствуйте, Умака Кумакаки, Вы писали:
УК>Здравствуйте, tryAnother, Вы писали:
A>>гарантий никто и не обещал, это понятно, но сеть там точка точка, никого кроме железки и принимающей машины нет, и она работает хорошо, пакетам там теряться некуда. УК>рация работает на бронепоезде? речь не о потерях, а о порядке пакетов
рация конечно на танке, но тест результаты теста показывают не перетасовку пакетов, а именно потерю
если бы проблемы была в потере порядка, то сообщения изобиловали как положительными так и отрицательными dif
A>>вопрос в сравнении подходов к чтению: A>>- синхронный и асинхроный asio теряют пачки по единицам — десяткам пакетов от единиц до десятков в час в зависимости от загрузки машины, A>>- подход с чтением через sleep теряет единицы пакетов в сутки
УК>ещё раз, текущий тест "потери пакетов" невалиден, инкремент счётчика тут не канает, если надо посчитать количество принятых пакетов — заведи vector<bool> например на миллион элементов, и засовывай ему по индексу, который равен id пришедшего пакета true, после отправки миллиона пакетов выждать паузу и посчитать, сколько в векторе true значений, если меньше миллиона, то есть потери.
счетчик не только инкриминируется (перед проверкой), но и устанавливается на последний принятый номер (после проверки)
что обеспечивает оба типа проверки, и потерю и перестановку.
и перестановки действительно ловились но проблема тогда была в самой железке, сейчас их нет.
и при чтении с задержкой и потерь почти нет.
еще какие предположения по невалидности теста? и почему он проходит в режиме с задержкой
Здравствуйте, tryAnother, Вы писали:
A>еще какие предположения по невалидности теста? и почему он проходит в режиме с задержкой
я бы всё таки убрал аллокацию, возможно после тысяч аллокаций аллокатор решает компактифицировать кучу, или ещё какую оптимизацию провести, и на этот момент попадает передача пакета. Убери вообще класс calcstat, сделай просто свободную функцию OnPacket(const boost::system::error_code& error, std::size_t bytes_transferre), внутри неё статическую переменную int seq_ и передавай этот хендлер просто как есть, без оборачивания в функтор через bind
Дополнительные тесты показали. что дело было не в бобине asio, а в размере буфера чтения сокета, если для примеров с asio установить буфер как для read_periodic, то потерь пакетов не наблюдается, однако есть разница в загрузке ЦП, так в примерах с asio загрузка примерно раза в 2-3 выше по сравнению с read_periodic.
Однако теперь потребовалось реализовать чтение и на Linux.
Написал программку, которая разными способами вычитывает поток UDP пакетов.
Способы такие:
ReadSync, простое синхронное чтение (receive_from) в цикле
NoBlock (NoBlock with Sleep), сокет в неблокирующем режиме, читает пока есть данные, если данных нет, то или опять читает, или засыпает на 10 мс.
ASync (ASync with Sleep) асинхронное чтение через async_receive_from, но в обработчике запускается синхронное чтение в неблокирующем режиме, пока не кончатся данные, потом опять async_receive_from, (или через задержку 10 мс)
RecvMsg, на Linux реализован дополнительный способ чтения через recvmmsg.
Данные генерировались программой, там socket.send_to в цикле.
Тестировалось на Win10 и wsl (Ubuntu 22).
Для запуска на Linux нужно увеличить максимальный размер входного буфера командой
sudo sysctl -w net.core.rmem_max=4194304
Иначе идут постоянные потери.
Результаты сомнительной правдивости, тк при запуске чтения в разных режимах менялась и скорость отдачи пакетов, поэтому в результатах программы показывают разные скорости приема, а ошибок счетчика при этом нет. Но можно сравнить загрузку ЦП.
Почти никакая загрузка ЦП в режиме «Non Block with Sleep» на windows, на Linux этот режим тоже вырывается вперед по использованию ЦП.
на Linux скорость передачи в среднем кратно выше.
режим RecvMsg должен по идее всех обгонять, а он показал худшие результаты.
В целом поток получается не большой, на localhost с пакетом 1024 байта получается 650 Mbits/sec на Windows, поэтому попробовал утилиту iperf3:
— сервер: iperf3 -s
— клиент: iperf3 -c 127.0.0.1 -u -b 0 -l 1024
Показывает схожие скорости на таком размере буфера.
Если размер пакета поставить по умолчанию (128 k) то скорость 25 Gbits/sec, при 50000 pps.
То есть видно что ограничение не в ширине канала, а в количестве системных вызовов.
По идее recvmmsg как раз и сделан чтобы оптимизировать этот вопрос, он читает много пакетов за раз, но почему то это не помогло.
1 тюнить буферы системе нет смысла если есть возможность из задать через настройки сокета
2 тюнить надо и исходящий буфер на сокете а не только входящий
3 recvmmsg не нужен, он есть в asio для линукса и для винды, надо всего лишь брать массив строк векторов итд, лимит 64
4 io_context run надо крутить не в одном потоке, а на максимум воркеров по доступным для системе реальных ядрах
5 под линуксом есть еще uring, если апи находится при компиляции
6 в asio есть еще и как минимум три вида сокетов на корутинах, где они в тестах
Здравствуйте, reversecode, Вы писали:
R>1 тюнить буферы системе нет смысла если есть возможность из задать через настройки сокета
настройки сокета позволяют увеличивать буфер только до некоторой границы установленной в OS,
для win дает установить хоть ГБ, а в linux по умолчанию верхняя граница 208 КБ, можно посмотреть командой
sysctl net.core.rmem_max
те на 50000 п/с заполняется за 4 мс, и идут потери, я в примере ставлю 4 МБ, хватает на 80 мс (больше гранулярности переключения потоков?)
R>2 тюнить надо и исходящий буфер на сокете а не только входящий
в целом можно, но у меня отдающая утилита только для тестов, пока в доступе нет реальной железки,
но проверил, при установке буфера в 4 МБ на первый взгляд ничего не поменялось.
R>3 recvmmsg не нужен, он есть в asio для линукса и для винды, надо всего лишь брать массив строк векторов итд, лимит 64
поискал в 85 версии буста, не нашел вызова recvmmsg, гугл говорит что есть сторонние патчи, но их не проверял
да и получается что recvmmsg дает не лучшие результаты
R>4 io_context run надо крутить не в одном потоке, а на максимум воркеров по доступным для системе реальных ядрах
да, это я понимаю.
но у меня задача скорее не получить максимальный поток, а лучшим образом читать заданный.
предполагается что передача будет в пределах 30000-60000 пакетов/с, и неблокирующее чтение со слипом показывает минимальное потребление ЦП
R>5 под линуксом есть еще uring, если апи находится при компиляции
спасибо, поробую посмотреть
R>6 в asio есть еще и как минимум три вида сокетов на корутинах, где они в тестах
корутины это не лишний слой абстракции над асинхронными вызовами?
они реально могут дать выигрыш производительности?
Здравствуйте, tryAnother, Вы писали:
A>Здравствуйте, reversecode, Вы писали:
R>>1 тюнить буферы системе нет смысла если есть возможность из задать через настройки сокета
A>настройки сокета позволяют увеличивать буфер только до некоторой границы установленной в OS, A>для win дает установить хоть ГБ, а в linux по умолчанию верхняя граница 208 КБ, можно посмотреть командой A> sysctl net.core.rmem_max A>те на 50000 п/с заполняется за 4 мс, и идут потери, я в примере ставлю 4 МБ, хватает на 80 мс (больше гранулярности переключения потоков?)
да, забыл что в линуксе оно еще и лимитом
R>>2 тюнить надо и исходящий буфер на сокете а не только входящий
A>в целом можно, но у меня отдающая утилита только для тестов, пока в доступе нет реальной железки, A>но проверил, при установке буфера в 4 МБ на первый взгляд ничего не поменялось.
тогда логика не понятна
тюнить входящий не тюня исходящий
на отправку пакеты тоже могут дропнуться если буфер будет переполнен
R>>3 recvmmsg не нужен, он есть в asio для линукса и для винды, надо всего лишь брать массив строк векторов итд, лимит 64
A>поискал в 85 версии буста, не нашел вызова recvmmsg, гугл говорит что есть сторонние патчи, но их не проверял A>да и получается что recvmmsg дает не лучшие результаты
да спутал одну m, только патчи есть
https://lists.boost.org/Archives/boost/2023/01/253873.php https://blog.cloudflare.com/how-to-receive-a-million-packets
R>>4 io_context run надо крутить не в одном потоке, а на максимум воркеров по доступным для системе реальных ядрах
A>да, это я понимаю. A>но у меня задача скорее не получить максимальный поток, а лучшим образом читать заданный. A>предполагается что передача будет в пределах 30000-60000 пакетов/с, и неблокирующее чтение со слипом показывает минимальное потребление ЦП
я совсем не понял что это значит
R>>5 под линуксом есть еще uring, если апи находится при компиляции
A>спасибо, поробую посмотреть
если нет смысла получить максимальный, то нет смысла и смотреть
R>>6 в asio есть еще и как минимум три вида сокетов на корутинах, где они в тестах
A>корутины это не лишний слой абстракции над асинхронными вызовами? A>они реально могут дать выигрыш производительности?
только в случае когда логика размазывается на потоки в случаях воркеров
спасибо, посмотрю
R>я совсем не понял что это значит
синхронное чтение утилизирует CPU на 40% при 66 kpps, а не блокирующее чтение со сном 1% на 87 kpps
второй вариант лучше, так по потоку проходит, и потребляет меньше ЦП.
тут тестируется только прием, обработка будет тоже затратная и лучше оставить ей больше ресурсов.
в случае когда есть затратная обработка
то это пальцем в небо пытаться просчитывать ее сейчас методом анализа простоя cpu%
в целом вы ерундой занимаетесь
потому что надо воркеры
раскидывать по треидам
и каждый воркер будет заниматься разгребанием затратной обработкой
это asio и умеет делать
достаточно run запустить на потоках больше одного, плюс синхронизации если там нет разных сессий а просто один поток от одного девайса
и не важно как и сколько там будет оставаться cpu
в общем задача такая:
— есть железка, которая снимает показания датчиков (вроде линейной камеры) упаковывает в поток UDP и отправляет,
— приложение этот поток собирает вычищает весь мусор, остатки обрабатывает и передает дальше подписчикам уже намного меньше информации.
специфика фильтрации и обработки в том, что используется предыдущее состояние, те "кадры" нужно обрабатывать последовательно, или хотя бы группами.
поэтому и вычитывание в один поток, чтобы потом из разных очередей не собирать.
понятно что UDP может пакеты перемешать, и для этого есть небольшой буфер на 10 пакетов где они сортируются,
но в процессе эксплуатации предыдущей версии железки и ПО такое если и встречалось, то очень очень редко. может единицы раз на годы работы.
сейчас поток вычитывает пакеты, собирает в блоки, и отдает другому потоку на обработку, обработка занимает примерно 50-80% ядра.
но данных хотят гнать больше, и соответственно загрузка вырастет.
а обработка происходит на обычном ПК, позиционируется что чуть ли не на ноутбуке этим можно будет заниматься, от того и интерес использовать поменьше ЦП если получится.