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)
Ответ на: комментарий от olelookoe

я выкинул всю логику и оставил там просто спать 1000

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

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

Да пойми ты уже, тебе больше 30 потоков не надо. От слова СОВСЕМ. Каждому потоку выделятся ОЧЕНЬ большой квант времени на работу. И этот квант времени ты и должен заполнять. Переписать надо

if (auto event = XXX_events_.pop(); event) {

на

auto events = XXX_events_.get_many_events(/* min_events */ 1000, /* timeout */ 20ms);

И лопатить их всех разом. И без лишних системных вызовов, аля «сколько времени точно».

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

так не выйдет

там у каждого бинарника ивенты свои

и входные данные свои

похоже задача в принципе мало решаемая

и нужно просто довольствоваться 1500 процессами да и всё(

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

я думал победить это нарасчиванием cpu
но оно помогает не сильно
даже на 64\128 core cpu

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

похоже задача в принципе мало решаемая

таким способом. хайлоад так не делается, увы.

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

там у каждого бинарника ивенты свои

Значит каждый такой «бинарник» должен стать отдельным объектом.

ивенты свои и входные данные свои

похоже задача в принципе мало решаемая

Прекрасно решается через event loop или модель акторов.

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

*мало решаемая без существеной переписки логики*

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

если никто не делает - это не значит что это никому не нужно

Зачем? Посмотрите на свои load avgs, посмотрите на scheduler stats и сколько там involuntary context switches. Ну, уперлись Вы в потолок by CPU, дальше то что? Оно имеет смысл только если Ваши процессы большую часть времени спят и очевидно это на Ваш случай. Точнее их слишком много при типичном кванте порядка миллисекунды, и context switches сами по себе вещь не бесплатная.

Вы явно что-то делаете не так.

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

Так же как в Bash сделать вряд ли получится, потому что Bash может просто использовать блокирующее чтение на stdin и никаких проблем из-за этого не будет. Но вполне можно сделать N нитей обработки, которые ждут события на очереди, читают задания и выполняют. Сама обработка у тебя уже наверняка есть. Осталось дописать довольно простой кусочек кода, распределяющий задачи.

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

если никто не делает -это не значит
что это никому не нужно

Так не делают с обычными потоками, потому что все ресурсы уходят в накладные расходы. Но примерно так делают в Go, где собственный планировщик жонглирует горутинами.

i-rinat ★★★★★
()

Если полезной работы немного, то твои процессы впустую переключают контексты, отсюда такая нагрузка на ядро. Попробуй вариант с увеличением времени ожидания, если конкретный процесс не был нагружен работой. Процесс поспал 10мс, проверил очередь событий и выяснил, что делать ему нечего, и засыпает на 20мс. Если через 20мс опять работы нет, идёт спать на 40мс. Проснулся - опять делать нечего - можно поспать 80мс. И так далее в геометрической прогрессии. Существенный минус - при таком раскладе есть шанс, что все процессы уйдут в долгую спячку, и нужно будет придумывать какой-то способ их всех распинать. Сам смотри, насколько это релевантно для твоего случая.

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

Господин возжелал 7000 потоков будить каждые 10 миллисекунд. Очевидно железо не тянет. А самое главное - зачем непонятно. Откуда взялась цифра 10 millis? Почему не 100? Очень похоже на poll (что имеет право на жизнь в true low-latency env, но там sleeps вообще не предполагаются, начинают cores изолировать, потоки гвоздями прибивать etc) где реально нужен push. Мои 2 копейки.

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

Но примерно так делают в Go, где собственный планировщик жонглирует горутинами

…а также в Erlang и производных.

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

Господин возжелал 7000 потоков будить каждые 10 миллисекунд.

И, кстати, если хочется просыпаться с фиксированной avg freq (jitter по-любому будет не детский) - привязываются к abs времени, а не к desiredPeriod - handlingTime.

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

доступ к какому-то общему ресурсу?

Который доступ ТС еще и толком не показывает как сделан :) что может пойти не так... да примерно всё.

slackwarrior ★★★★★
()

не надо переизобретать даже не велосипед, а каменный топор.

ивенты ВСЕГДА принимаются с ожиданием на неких примитивах синхронизации,и поток просыпается «сразу» (с учетом приоритетов и всякой внутренней кухни ОС), то есть выводится из ожидания и ему передается управление системой.

ивенты НИКОГДА не принимаются поллингом со слипом(поллинг без слипа это тупо зацикливание и 100 загрузка проца). прием поллингом это отказ от примитивов синхронизации которые для ТОГО И ПИСАЛИСЬ, чтобы делать event-driven модель - то есть принимать асинхронные ивенты максимально эффективно.

карочи никаких sleep() чтоб не было.

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

Нет. Треды тоже сожрут на переключении контеста. Тут только число процессов по числу ядер/потоков и мультиплексирование. Остальное от лукавого. У вас система будет работать на переключение.

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

К 7000 (N) бинарям, контекст которых нужно переключать ещё и M тредов? Вы совсем того? :)))) У вас система на переключение контекста тупо работает, а не полезную работу делает.

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

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

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

и поток просыпается «сразу»

В вашей альтернативной вселенной?? На какой time-scale вы «живёте»? Или мы очередного «сферического коня в вакууме» обсуждаем?

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

а что происходит в вашей вселенной?

В моей вселенной любой wake up занимает конечное время, существенно большее нуля. И если вы не уже на CPU, то скорее всего вы условным конкурентам слили. Как то так. Своя специфика, я уверен у многих по другому.

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

Дурь у него в башке, имхо. Для таких писак придумали Go - вот там есть где развернуться с горутинами. Так что пусть переписывает))

menangen ★★★★★
()

+1 к идее треда, что 7000 потоков – просто дурацкая архитектурная идея и её надо переделать.

unDEFER ★★★★★
()

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

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

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

я думаю добавить что-то вроде


while (true)

if(trigger_flag)
 {
//тут обработка ивентов
}

sleep(1 секунда)



но тогда раз в секунду будет дёргаться if
проверю насколько это ресурсозатратно

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

да я и 1000 пробовал

а будить мне их не нужно

похоже if таки нужно добавить

чтобы не опрашивать ивенты

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

такой уж код)

нужно как-то не переключать контектсы

smilessss ★★★★★
() автор топика
  1. Как уже сказали, кэшей не хватает.
  2. У тебя ядро может тупо не успевать переключать процессы. Какое у тебя значение таймера?

Тебе бы нормальный event loop и очередь сделать, это скорее всего решит проблемы. Ну либо перепиши на язычке с green threads.

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

CONFIG_HZ=250

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

Вангую - будет хуже. Но вы отпишитесь потом…

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

ну вообще с очередями в ивентлупе оно и должно так работать без всяких слипов

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

Оно ведь как. Можешь — скинь. Может для решения твоей проблемки мсьям контекста не хватает. Или нет, не скидывай@интригуй :)

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

есть догадки что такая очередь во всем виновата

буду думать

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;
    };

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

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

Ну, то есть, там синхронизация потоков через sleep().

Классика! Люблю такое!

Говорю же, надо просто нормальный event loop из библиотечки типа libuv взять и перестать городить этот хтонический кошмар. Всё уже давно написано за вас.

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

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

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

Вообще-то там выше кокой-то лок-гард с mutex... хотя почему обращение через this — хз. Может там еще какое-то кривое наследование порылось, если через this приходится различать.

slackwarrior ★★★★★
()
Последнее исправление: slackwarrior (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.