LINUX.ORG.RU

Помогите с анализом нагрузки на ЦПУ в сетевом приложении

 , ,


1

5

Есть сетевое приложение, снял трейс с помощью perf, вышло, что 35% ЦПУ оно проводит в приеме сообщений, 50% в передаче. Передается порядка 110 сообщений в секунду размером в основном от 100 до 500 байт. Т.е. данных явно мало и нагрузка на ЦПУ явно этому не соответствует. Нужно разобраться почему так.

Что бросается в глаза - в приеме только 5% из 35% оно проводит в сисколах ядрах, а при передаче уже 26% из 50%. Разве так должно быть? И самое главное - из 26% ЦПУ, что уходят на _libc_sendmessage (которая уже дергает ядро), около 19% уходит на ip_recv - это же по идее уже прием данных, а не передача? а вообще стек вызова заканчивается вызовом __lock_text_start, где ЦПУ проводит 17.8% времени

есть знатоки линукса и его ядра, кто может пояснить: 1 Нормально ли тратить при передаче 100 сообщений в 100-500 байт столько ЦПУ (запускаю на виртуалке, на хосте рязань 9 5950х) 2. Почему в вызове _libc_sendmessage используется ip_recv и в итоге подавляющее кол-во времени проводится в __lock_text_start

Из подробностей - обмен по мультикасту, создается несколько отдельных сокетов, которые привязываются к одному мультикаст адресу, под капотом boost::asio.

UPDATE: добавил ссылку на FlameGraph со стеком вызовов при отравке сообщения. Тут видно, что __libc_sendmessage занимает 9% от общего времени работы приложения, и из них 6.35% ЦПУ проводит в __lock_text_start. Возможно это скажет кому-то из специалистов?

★★

Последнее исправление: yetanother (всего исправлений: 1)

Т.е. данных явно мало и нагрузка на ЦПУ явно этому не соответствует.

А в чём проявляется нагрузка на ЦПУ? Приложение хотя бы в top видно?

Ещё стоит иметь в виду, что perf измеряет не общее время, проведённое внутри функций (wallclock), а только активное использование процессора. В сетевом приложении по-идее большую часть времени должно занимать ожидание событий, которое perf вообще не видит

И самое главное - из 26% ЦПУ, что уходят на _libc_sendmessage (которая уже дергает ядро), около 19% уходит на ip_recv - это же по идее уже прием данных, а не передача? а вообще стек вызова заканчивается вызовом __lock_text_start, где ЦПУ проводит 17.8% времени

Типичная работа event loop. Одна операция закончилась, переключаемся на следующую, ничего не осталось — ждём.

annulen ★★★★★
()
Ответ на: комментарий от zx_gamer

Это легаси, но доступ к исходникам есть. Судя по всему в цикле блокирующий сокет крутится. Но по приему и выглядит все хорошо - прием данных по сети потребляет 35%, из них 5% непосредственно на прием в сисколе ядра, а 30% уходит на обработку данных. Вопрос больше по передаче

yetanother ★★
() автор топика
Ответ на: комментарий от annulen

А в чём проявляется нагрузка на ЦПУ? Приложение хотя бы в top видно?

Нагрузка проявляется в потреблении ресурсов ЦПУ)) В топе, конечно, видно. Где-то 60-70% жрет

В сетевом приложении по-идее большую часть времени должно занимать ожидание событий, которое perf вообще не видит

У меня и задача снизить потребление ЦПУ, если что-то где-то ждет, то это не страшно

Типичная работа event loop. Одна операция закончилась, переключаемся на следующую, ничего не осталось — ждём.

Активное ожидание? Не хотелось бы, ресурсы-то ограниченные

yetanother ★★
() автор топика
Ответ на: комментарий от yetanother

У меня и задача снизить потребление ЦПУ, если что-то где-то ждет, то это не страшно

Ждать можно по разному. Пока не покажешь какая у тебя логика работы с сокетом и какая мощность виртуалки (может у тебя там одно ядро всего) - никаких внятных ответов тебе никто не даст.

Norgat ★★★★★
()
Ответ на: комментарий от zx_gamer

Осторожно предположу, что я все-таки ответил на вопрос - крутится блокирующийся сокет boost::asio в отдельном потоке. То есть в явном виде нет ни select, ни poll, ни epoll, ни kqueue. По первому скажу еще что его уже давно никто не использует, уж слишком старый он. По последнему замечу, что речь идет все-таки о линукс. Ну и вроде как линукс давно уже epoll использует. Думаю, что у boost::asio под капотом именно он, но это мое предположение.

Но если я все-таки не ответил на вопрос, то прошу уточнить, что именно нужно еще рассказать. Заранее спасибо

yetanother ★★
() автор топика
Ответ на: комментарий от Norgat

На передачу используется сокет boost::asio, все тривиально

   _socket->send_to(...);

На прием крутится блокирующий сокет в отдельном потоке

    try
    {
        boost::system::error_code ec;
        while (true)
        {
            size_t bytesReceived = _socket->receive_from(
                                       boost::asio::buffer(_buffer),
                                       _endPoint,
                                       boost::asio::ip::udp::socket::message_flags(),
                                       ec
                                   );

            boost::this_thread::interruption_point();

            _callback(_buffer.data(), bytesReceived);
        }
    }
    catch (const boost::thread_interrupted&)
    {
       // ...
    }

Но по приему у меня вопросов нет, там есть что оптимизировать в обработке. Вопрос по передаче, почему она столько времени в ядре проводит. По машине писал, на хосте amd Ryzen 9 5950x

yetanother ★★
() автор топика
Ответ на: комментарий от ya-betmen

Sleep не поможет. Если бы там был 100% нагрузка вне зависимости от кол-ва входящий сообщений, то другое дело. Но тут нет 100% нагрузки и если уменьшать число сообщений, то и нагрузка уменьшается. Луп то инфинити, но там же блокирующийся сокет

yetanother ★★
() автор топика
Ответ на: комментарий от yetanother

500*110/1024=50Kb

Если бы прием отправка таких обёмов так тормозили вообще бы ничего не работало.

запускаю на виртуалке

Может там с сетевой карточкой фигня какая?

ya-betmen ★★★★★
()
Ответ на: комментарий от ya-betmen

Если бы прием отправка таких обёмов так тормозили вообще бы ничего не работало.

Отсюда и вопрос, что не типичное это поведение.

Может там с сетевой карточкой фигня какая?

Не, именно с кодом, воспроизводится на разных машинах

yetanother ★★
() автор топика

Добавил ссылку на FlameGraph со стеком вызовов при отравке сообщения. Тут видно, что __libc_sendmessage занимает 9% от общего времени работы приложения, и из них 6.35% ЦПУ проводит в __lock_text_start. Возможно это скажет кому-то из специалистов?

yetanother ★★
() автор топика
Ответ на: комментарий от anonymous

Это UDP, мультикаст, там же не должно быть подтверждения. Приемник принимает устойчиво, сеть хорошая. Единственно, что как я понял, там создается несколько сокетов, которые привязываются к одному мультикаст адресу, может они как-то конкурируют друг с другом в ядре и поэтому такая ситуация?

yetanother ★★
() автор топика
Ответ на: комментарий от yetanother

Значит источник и приемник на одной системе, и график показывает весь процесс посылки и приема сообщения.

anonymous
()
Ответ на: комментарий от anonymous

Мультикаст заворачивается на самого себя, так по умолчанию. Источник и приемники на одном хосте, но в разных приложениях. График показывает стек вызовов одного потока приложения, который работает на передачу, их там еще два таких с таким же стеком вызовов плюс два потока на прием сообщений, но там уже не мультикаст и с ними вопросов нет, в принципе

yetanother ★★
() автор топика
Ответ на: комментарий от yetanother

Не знаю, на графике показаны одновременно и посылка и прием. Прием висит на блокировке, наверно, пока принимающий поток не заберет данные.

anonymous
()
Ответ на: комментарий от yetanother

Почему бы и нет, если это на одном хосте: минимум сисколов и переключений контекста.

В общем, основное время висит на приеме, и чуть - дергает модуль сетевой карты. Остальное время не имеет значения.

anonymous
()
Ответ на: комментарий от yetanother

И я угадал «__lock_text_start» - это где-то внутри spinlock

anonymous
()
Ответ на: комментарий от yetanother

Ну и вроде как линукс давно уже epoll использует.

Ядро Linux ничего не использует. Он предоставляет разные способы взаимодействия с собой.

Среди этих способов: 1. POSIX, то, что умеет любая *NIX система: select и poll 2. специфичное только для Linux: epoll

Вот что выберет программист, то и будет использоваться, отсюда, кстати, неверно, что select не используется. Это самый «классический» способ взаимодействия с *NIX системой.

Думаю, что у boost::asio под капотом именно он, но это мое предположение.

В Boost.ASIO будет ровно, то, что задано макросами: https://www.boost.org/doc/libs/1_85_0/doc/html/boost_asio/using.html

Далее, вы уверены, что это проблема где-то в системе, а не в Boost? Я бы написал «hello world» на чистых системных вызовах на Си, померил бы производительность там. Не нулевая вероятность, что это оверхед Boost.ASIO.

Ну а если проблема не в этом, то дальше надо думать над самой логикой (не лучше написать асинхронно?).

zx_gamer ★★★
()

Попробуй увеличить буфер на отправку.

faq2
()

Из подробностей - обмен по мультикасту, создается несколько отдельных сокетов, которые привязываются к одному мультикаст адресу, под капотом boost::asio.
UPDATE: добавил ссылку на FlameGraph

Выкинь всё, кроме работы с boost::asio, и выложи компилирующийся исходник. Иначе тупняк в треде растянется на пару месяцев.

LamerOk ★★★★★
()

Непонятно, зачем вообще Asio, если всё синхронно. Сделайте на сисколлах, как предлагали выше.

anonymous
()

пересобрать с новым asio который умеет uring

anonymous
()

Новая вводная. Я ошибся с оценкой сетевой нагрузки на порядок - там не 110 исходящий сообщений в секунду, а ~2750, размер средний 386 байт уже со всей службной информацией, трафик около 1Мб/сек получается. Приложение шлет мультикаст по двум интерфейсам и, соответственно, назад получает все эти же пакеты. Получается сетевая подсистема ядра просто ддосится мелкими пакетами? Сейчас буду дорабатывать архитектуру, там половину сообщений можно выбросить (дубли), и попробую еще сократить их кол-во.

С другой стороны, если брать 2750 пакетов с размером MTU 1400 это будет ~3,85 Мб/сек, это явно не предел сетевой подсистемы

yetanother ★★
() автор топика
Ответ на: комментарий от zx_gamer

Про epoll в линуксе я неправильно выразился, не линукс его использует, а в линуксе он используется. Да, в выхлопе перфа я нашел, что именно epoll используется в моем конкретном случае, но ЦПУ в нем очень мало времени проводит.

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

yetanother ★★
() автор топика
Ответ на: комментарий от yetanother

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

zx_gamer ★★★
()
Ответ на: комментарий от yetanother

Можно еще для эксперимента собрать текущий код на FreeBSD (естественно, убедившись, что в Boost.ASIO включится kqueue).

По крайней мере раньше провайдеры предпочитали FreeBSD, потому что сетевая подсистема ни в какое сравнение с Linux'овой не шла.

zx_gamer ★★★
()
Ответ на: комментарий от zx_gamer

Все равно остается вопрос - ведь если сообщения будут размером с дефолтный MTU, то 2750*1400 будет все равно 30 МБит/сек. Т.е. это не такое уж большое кол-во пакетов. Ну либо для нагрузки 100 Мбитного канала нужно всегда увеличивать МТУ в 3-4 раза - я тут не в курсе

yetanother ★★
() автор топика
Ответ на: комментарий от zx_gamer

Я согласен, что кол-во сообщений играет роль, но кмк не должны 2750 сообщений быть такой большой нагрузкой.

yetanother ★★
() автор топика
Ответ на: комментарий от yetanother

Если для обмена сообщениями действительно используется несколько сокетов (а не несколько сотен или тысяч сокетов), то я бы попробовал переключить с epoll на poll и сравнить.

annulen ★★★★★
()

Переделал на асинхронщину, картинка в перфе, соответственно, поменялась заметно, но нагрузка на ЦПУ осталась. Просто раньше ЦПУ проводил время в потоках, которые вели сетевой обмен через блокирующиеся сокеты. А теперь их нет и ЦПУ проводит время в io_service.

yetanother ★★
() автор топика
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.