Есть ли проблемы с производительностью CSocket::Send?

#c #c #sockets #visual-c #concurrency

#c #c #сокеты #visual-c #параллелизм

Вопрос:

ОБНОВЛЕНИЕ 14 июня 2011

Быстрое обновление… Большинство респондентов сосредоточились на хитроумном методе обработки очереди сообщений, подлежащих регистрации, однако, хотя там, безусловно, не хватает оптимизации, это, безусловно, не корень проблемы. Мы переключили выход в короткий режим ожидания (да, выход действительно привел к 100%-му процессору после того, как система перестала работать), однако система по-прежнему не может следить за протоколированием, даже когда он и близко не подходит к этому режиму сна. Из того, что я вижу, отправка просто не очень эффективна. Один респондент прокомментировал, что мы должны заблокировать Send () вместе в to one send, и это казалось бы наиболее подходящим решением более крупной базовой проблемы, и именно поэтому я пометил это как ответ на исходный вопрос. Я, конечно, согласен, что модель очереди очень несовершенна, поэтому спасибо за отзыв по этому поводу, и я поддержал все ответы, которые внесли свой вклад в обсуждение.

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

Еще раз спасибо за все отзывы.

ОРИГИНАЛЬНЫЙ ВОПРОС

В нашей системе у нас есть два компонента, важных для решения этой проблемы — один разработан на Visual C , а другой на Java (не спрашивайте, исторические причины).

Компонент C является основной службой и генерирует записи в журнале. Эти записи журнала отправляются через CSocket::Send в службу ведения журнала Java.

Проблема

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

Если я попаду на сервер ведения журнала Java с помощью простого приложения на C #, я смогу запустить его намного быстрее, чем мне когда-либо понадобится, с помощью инструмента C , и это прекрасно работает.

В мире C функция, которая добавляет сообщения в очередь, является:

 void MyLogger::Log(const CStringamp; buffer)
{
    struct _timeb timebuffer;
    _ftime64_s( amp;timebuffer );

    CString message;
    message.Format("%dd,d,%srn", (int)timebuffer.time, (int)timebuffer.millitm, GetCurrentThreadId(), (LPCTSTR)buffer);

    CString* queuedMessage = new CString(message);
    sendMessageQueue.push(queuedMessage);
}
  

Функция, запускаемая в отдельном потоке, который отправляет в сокет, является:

 void MyLogger::ProcessQueue()
{
    CString* queuedMessage = NULL;
    while(!sendMessageQueue.try_pop(queuedMessage))
    {
        if (!running)
        {
            break;
        }
        Concurrency::Context::Yield();
    }

    if (queuedMessage == NULL)
    {
        return;
    }
    else
    {
        socket.Send((LPCTSTR)*queuedMessage, queuedMessage->GetLength());
        delete queuedMessage;
    }
}
  

Обратите внимание, что ProcessQueue многократно запускается самим потоком внешнего цикла, что исключает кучу бессмысленной преамбулы:

 while(parent->running)
{
    try
    {
        logger->ProcessQueue();
    }
    catch(...)
    {
    }
}
  

Очередь:

 Concurrency::concurrent_queue<CString*> sendMessageQueue;
  

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

Является ли это ограничением CSocket:: Send, которое делает его менее полезным для нас? Неправильное его использование? Или это отвлекающий маневр, и проблема кроется в другом?

Я очень ценю ваш совет.

С уважением

Мэтт Педдлсден

Комментарии:

1. Попробуйте закомментировать Yield().

2. Если я закомментирую результат, система будет работать на 100% CPU. Я бы подумал, что если бы что-то было в очереди, то try_pop вернулся бы, и это никогда даже не дошло бы до Yield? Или я что-то упускаю … ?

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

4. Хорошо, я приму этот отзыв, и мы рассмотрим его — однако, если система сейчас в основном простаивает (нерабочее время), а журнал устарел примерно на 1 час (около 150 МБ памяти) — почему сейчас он не может очень быстро его удалить? Я бы предположил, что ситуация, которую вы описываете, будет означать, что мы отстаем в течение дня, но быстро догоняем, как только система успокаивается. Как это происходит, система регистрируется с той же скоростью, что и весь день. В этом случае мы каждый раз получаем что-то обратно от try_pop и, предположительно, регистрируем это в каждом цикле потока? или я что-то упускаю?

5. Что такое «цикл потока»? Если эта функция вызывается непрерывно в каком-либо другом цикле потока, то я бы ожидал, что эта функция и вызывающий ее цикл будут использовать процессор на 100%, когда остальная часть системы простаивает, независимо от того, включена функция Yield() или нет. Вызов Yield() просто немедленно запланировал бы повторное включение этого потока, потому что больше нечего делать. Это очень сбивает с толку!

Ответ №1:

Что ж, вы могли бы начать с использования блокирующей очереди производитель-потребитель и избавиться от «Выхода». Я не удивлен, что сообщения блокируются — когда одно из них публикуется, поток регистратора обычно находится в загруженной системе, готов, но не запущен. Это приведет к значительной задержке, которой можно избежать, прежде чем любое сообщение в очереди сможет быть обработано. Фоновый поток than имеет возможность попытаться избавиться от всех сообщений, которые накопились в очереди. Если в загруженной системе много готовых потоков, вполне может быть, что потоку просто не хватает времени для обработки сообщений. особенно, если много накопилось и в розетке.отправляйте блоки.

Кроме того, почти полная трата одного ядра процессора на опрос очереди не может быть хорошей для общей производительности.

Rgds, Мартин

Комментарии:

1. Я понимаю, что выход означает, что мы не используем избыточный процессор — действительно, на данный момент на действующем сервере, который в настоящее время регистрируется на 1 час позже реального времени, загрузка процессора составляет около 0%, и я не видел какой-либо аномальной загрузки процессора, чем до этого изменения за весь день. Журналы постоянно поступают на сервер ведения журнала с той же скоростью, что и в течение всего дня. Таким образом, в этом случае доходность даже не достигается. Я должен признать, что нашим намерением с этой реализацией было попытаться устранить все блокировки — но мы, безусловно, могли бы попробовать переключиться на реализацию, которая использует ожидание / уведомление.

2. О! Подождите .. что тогда вызывает ProcessQueue? Есть ли другой цикл с какой-либо сигнализацией, которую вы не показали?

3. Это в третьем разделе кода в исходном вопросе — в основном, во время запуска logger-> processqueue.

4. Хорошо, итак, я получаю 5% за наблюдение <g>

5. Возможно, вы захотите избежать блокировки, но, учитывая concurrent_queue, там уже может быть блокировка (не уверен, что ваша реализация c_c свободна от блокировки или нет). В качестве первой попытки добавьте семафор, инициализированный нулем. Сообщите об этом после отправки сообщения в очередь и в потоке дождитесь его, прежде чем вызывать try-wait.

Ответ №2:

На мой взгляд, вы определенно смотрите не на самое эффективное решение. Вам обязательно следует вызвать Send() один раз. Для всех сообщений. Объедините все сообщения в очереди на стороне пользователя, отправьте их все сразу с помощью Send(), затем выдайте.

Кроме того, на самом деле это не так, как вы должны это делать. PPL содержит конструкции, явно предназначенные для асинхронных обратных вызовов — например, call object . Вы должны использовать это вместо того, чтобы сворачивать вручную самостоятельно.

Комментарии:

1. Ага, значит, вместо того, чтобы использовать одну функцию Send() на строку журнала, мы могли бы просто объединить их все вместе в одну большую CString и использовать одну функцию Send()? Мне нравится эта идея… Как насчет ограничений на отправку? Или это когда требуется многократный вызов Send () до тех пор, пока не исчезнут все байты (как обсуждалось в некоторых онлайн-примерах)?

2. Кроме того, я не совсем уверен, что вы подразумеваете под PPL — извините, если это основной вопрос! Не могли бы вы подробнее остановиться и на этом аспекте вашего ответа? Действительно, большое спасибо.

Ответ №3:

Здесь есть вещи, которые могут замедлить вас:

  • Используемая вами очередь.Я думаю, что это классический пример преждевременной оптимизации. Здесь нет причин использовать класс Concurrency::concurrent_queue, а не обычную очередь сообщений с блокирующим методом pop(). Если я правильно понимаю, классы параллелизма используют неблокирующие алгоритмы, когда в этом случае вы хотите заблокировать, пока очередь пуста, и освободить процессор для использования другими потоками.
  • Использование new и delete для каждого сообщения и внутренних распределений класса CString. Вам следует попробовать и посмотреть, поможет ли повторное использование сообщений и строк (с использованием пула) повысить производительность по двум причинам: 1. Распределение и освобождение объектов messages и string. 2. Распределений и освобождений, выполняемых внутри строк, возможно, можно избежать, если класс string будет внутренне перерабатывать свои буферы.

Ответ №4:

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

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

Комментарии:

1. Очередь сообщений является потокобезопасным ConcurrentQueue. Смотрите concurrent_queue . Система h никоим образом не привязана к процессору, нет.

2. Приношу извинения за многочисленные комментарии, возникли проблемы с программным обеспечением при использовании сайта! 🙂 — Профилирование еще не пробовал, я, к сожалению, не программист на C , так что это совершенно чужая территория для меня. Отправитель используется только в этом сценарии для ведения журнала, при любом другом использовании сокетов, которые у нас есть в системе, пропускная способность намного ниже, поэтому мы больше нигде не видели подобных проблем.