LINUX.ORG.RU

как оптимизировать с++ код, чтобы 7000 бинарников не выедали всё cpu

 , , , ,


2

3

https://imgur.com/RcrmzW0.png

https://imgur.com/Z4wdNBA.png

Код простой, в простое опрашивает ивенты, больше ничего не происходит.

- запускаю 1000-3000 бинарников - всё ок
- на 7000 бинарников - картина на скрине

Возможно у кого-то есть какие-то идеи куда смотреть и почему так просходит? откуда это ограничение в 7000

код очереди

std::optional<T> pop() {
        std::unique_lock<std::mutex> lock(this->mutex);

        if (q.empty()) {
            return std::nullopt;
        }

        std::optional<T> value = std::move(this->q.front());
        this->q.pop();

        return value;
    };


код опроса инвентов (он и генерит лоад)
while (true) {
        auto tick_start = std::chrono::steady_clock::now();

        if (auto event = internal_events_.pop(); event) {
            std::visit([this](auto &&casted_event) {
                process_event(casted_event);
            }, event.value());
        }

        if (auto event = my_events_.pop(); event) {
            using namespace td::td_api;
            auto &&object = event.value();
            switch (object->get_id()) {
                case updateMyActivity::ID:
                    process_event(move_object_as<updateMyActivity>(object));
                    break;
                case updateMyActivity2::ID:
                    process_event(move_object_as<updateMyActivity2>(object));
                    break;
                default:
                    break;
            }
        }

        if (auto event = my_q_events_.pop(); event) {
            std::visit([this](auto &&casted_event) {
                process_event(casted_event);
            }, event.value());
        }

        auto tick_end = std::chrono::steady_clock::now();
        auto duration = tick_end - tick_start;
        auto sleep_time = std::chrono::milliseconds(10) - duration;
        if (sleep_time.count() > 0) {
            std::this_thread::sleep_for(sleep_time);
        }
    }

★★★★★

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

судя по тому что много ядре - банально на засыпание/просыпание большие расходы.

Или увеличивать интерсвал с 10мc до большего (хотя бы в случаях когда не было событий в последний промежуток), или начинать вместо sleep(10ms) использовать сигнализирование через condition_variable

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

я пробовал ставить и

std::chrono::milliseconds(1000) - duration;


разницы никакой

идея с std::condition_variable
интересная

я так понял это лучше, чем

std::this_thread::sleep_for(sleep_time);

smilessss ★★★★★
() автор топика
Последнее исправление: smilessss (всего исправлений: 1)
Ответ на: комментарий от slackwarrior

почему на 3000 не слишком много и лоад авереж - 5
а на 7000 много всего?
почему именно 7000 а не 70000?

smilessss ★★★★★
() автор топика
Последнее исправление: smilessss (всего исправлений: 1)
Ответ на: комментарий от AntonI

А его и надо применять к реальной ОС :) Т.е. делать поправку на то что «процов вот стока». И хоть усрись, потери на переключении могут превысить «выигрыш от».

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

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

пусть спит всегда 10 мс или сколько там, и будет тебе счастье.

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

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

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

Может и не в ресурсах, а в некотором боттлнеке между :) Когда у нас архитектург притаскивал «неблокирующую очередь» много времени было потрачено на перепиливание ее велосипедов на стандартные атомики, а... толку не было, т.к. коннектор к базе строил потоки в очередь неявно :)

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

убрал только что

std::this_thread::sleep_for(1000);



теперь оно ест 100% в простое


попробую оставить только его без вычисления времени на поспать

smilessss ★★★★★
() автор топика
Последнее исправление: smilessss (всего исправлений: 1)

откуда это ограничение в 7000

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

А зачем тебе на 32-процессном сервере 7000 экземпляров приложения? Явно же будет плохо.

i-rinat ★★★★★
()
Ответ на: комментарий от smilessss

переключение контекста это затратная операция.

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

  • происходит очистка конвейера команд и данных процессора

  • очищается TLB, отвечающий за страничное отображение линейных адресов на физические.

  • содержимое кэша (особенно это касается кэша первого уровня), накопленное и «оптимизированное» под выполнение одного потока, оказывается совершенно неприменимым к новому потоку, на который происходит переключение.

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

вкратце - жопа. не делай так.

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

да ну йомайо

вот это

auto tick_end = std::chrono::steady_clock::now();
        auto duration = tick_end - tick_start;
        auto sleep_time = std::chrono::milliseconds(10) - duration;
        if (sleep_time.count() > 0) {
            std::this_thread::sleep_for(sleep_time);
        }

удали. вместо этого напиши

std::this_thread::sleep_for(std::chrono::milliseconds(10));

olelookoe ★★★
()
Ответ на: комментарий от i-rinat

там эти 7000 экземпляров приложения простаивают

они ничего не делают

там можно и 100 000 запустить

если не упереться в ram

смысл в том, что этот код на c++ - делает что-то не так

я и создал тред, чтобы может кто-то подсказал

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

этот код на c++ - делает что-то не так

Он опрашивает очередь сто раз в секунду вместо того, чтобы ждать на условной переменной события. Такой подход во время ожидания генерирует бесполезную нагрузку. А во время прихода сообщений ждёт до 10 мс перед обработкой сообщения. Если сообщение пришло вот сейчас, зачем ждать следующего «тика»? Дёрнул условную переменную, нить обработки проснулась. Всё равно в коде уже есть блокировки. (Они ведь есть, да? Да?)

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

Всё равно в коде уже есть блокировки. (Они ведь есть, да? Да?)

похоже что нет

попробую поставить

std::this_thread::sleep_for(std::chrono::milliseconds(1000));

может оно будет спать по секунде и не будет генерить такой лоад

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

на 7000 бинарников - картина на скрине

7000 процессов борются за 32 ядра? Переключение с процесса на процесс занимает много времени, а постоянные sleep это ещё и усугубляют. Тем самым процент потраченного времени процессора ОС увеличивается.

А тут ещё и internal_events_.pop() что предполагает доступ к какому-то общему ресурсу? Т.е. ещё и время на блокировку которое будет разруливать ОС?

Есть достаточно способов решать такие задачи в рамка пула потоков.

AlexVR ★★★★★
()

Твой проц захлёбывается вот и всё. Да и ядро тупо само в цикле перебирает все эти тысячи процессов бесконечно расшедуливая им вычислительное время. Там накладных расходов на существование всех этих процессов больше чем сами процессы затрачивают. Даже если они все спать будут ядро каждый из них всё равно будет передрыгивать.

Попробуй напиши другое приложение более тупорылое, почти пустое, но всё же что-то делающее и пусть каждый процесс спит. Вангую что ещё +1000 и снова будет такая же херня или даже сразу.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от AlexVR

софт писался изначально под 1 процесс

это я пробую его оптимизировать чтобы пускать больше одного процесса

на 1500-2000 проблем нет вообще


а вот дальше начинаются проблемы

smilessss ★★★★★
() автор топика
Последнее исправление: smilessss (всего исправлений: 1)
Ответ на: комментарий от LINUX-ORG-RU

7000 башей запускаются без проблем

за что там 70000 запускаются

это всё диалоги уровня «не запускай много процессов»

а если у меня есть cpu на 1024 ядра, там тоже много не запускать?

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

это я пробую его оптимизировать

...
        auto tick_end = std::chrono::steady_clock::now();
        auto duration = tick_end - tick_start;
        std::this_thread::sleep_for(duration);

искусственный разум нового поколения. больше тормозит - больше спит.

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

сделал по твоему совету


https://imgur.com/sxeXto7.png


на 6000 то же самое
на 7000 совсем грустно


похоже там дело в не в

auto sleep_time = std::chrono::milliseconds(10) - duration;

а в самом

std::this_thread::sleep_for(

попробую выкинуть его поменять на

std::condition_variable

но мне кажется этому коду врядли что-то поможет

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

А если 6000 но что-бы 55 гигов оперативы не сжирало.

И в htop отсортировать по потреблению времени цпу это процессы жрут или ядерные штуки?

Короче надо эксперементировать, яхз =)

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)