Как измерить и устранить узкие места переключения контекста?

У меня есть многопоточная программа сокетов. Я использую Boost Threadpool (http://threadpool.sourceforge.net/) для выполнения задач. Я создаю сокет клиента TCP для каждого потока в threadpool. Всякий раз, когда я отправляю большой объем данных, скажем, 500 КБ (размер сообщения), пропускная способность значительно снижается. Я проверил мой код для:

1) Ожидания, которые могут вызвать переключение контекста
2) Lock / Mutexes

Например, сообщение размером 500 КБ разделено на несколько строк, и я отправляю каждую строку через сокет с помощью :: send ().

typedef std::list< std::string > LinesListType;
// now send the lines to the server
for ( LinesListType::const_iterator it = linesOut.begin( );
it!=linesOut.end( );
++it )
{
std::string line = *it;
if ( !line.empty( ) && '.' == line[0] )
{
line.insert( 0, "." );
}

SendData( line + CRLF );
}

SendData:

void SendData( const std::string& data )
{
try
{
uint32_t bytesToSendNo  = data.length();
uint32_t totalBytesSent = 0;

ASSERT( m_socketPtr.get( ) != NULL )
while ( bytesToSendNo > 0 )
{
try
{
int32_t ret = m_socketPtr->Send( data.data( ) + totalBytesSent, bytesToSendNo );

if ( 0 == ret )
{
throw;
}

bytesToSendNo -= ret;
totalBytesSent += ret;
}
catch( )
{
}
}
}
catch()
{

}
}

Метод отправки в клиентском сокете:

int Send( const char* buffer, int length )
{
try
{
int bytes = 0;
do
{
bytes = ::send( m_handle, buffer, length, MSG_NOSIGNAL );
}
while ( bytes == -1 && errno == EINTR );

if ( bytes == -1 )
{
throw SocketSendFailed( );
}

return bytes;

}
catch( )
{

}
}

Вызов :: select () перед отправкой вызвал переключение контекста, поскольку :: select может блокироваться. Удержание блокировки на общем мьютексе заставило параллельные потоки ждать и переключать контекст. Это повлияло на производительность.

Есть ли лучший способ избежать переключения контекста, особенно в сетевом программировании? Я потратил по крайней мере неделю, пытаясь найти различные инструменты без удачи (vmstat, callgrind в valgrind). Какие-нибудь инструменты в Linux помогут измерить эти узкие места?

1

Решение

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

Как следствие, ваш подход к созданию потока для каждого соединения кажется простым способом поддерживать чистоту и разделенность, но он просто не масштабируется, поскольку включает слишком много ненужного переключения контекста. Вместо этого держите сеть в одном месте, если можете. Кроме того, не изобретайте велосипед. Есть такие инструменты, как, например, zeromq, который обслуживает несколько соединений, собирает целые сообщения из фрагментированных сетевых пакетов и вызывает обратный вызов только тогда, когда одно сообщение было полностью получено. И это так эффективно, поэтому я бы предложил использовать этот инструмент в качестве основы для вашего общения. Кроме того, он предоставляет множество языковых привязок, поэтому вы можете быстро создавать прототипы узлов, используя язык сценариев, и переключаться на C ++ для повышения производительности в дальнейшем.

Наконец, я боюсь, что библиотека, которую вы используете (что делает не кажется, является частью Boost!) является заброшенным, то есть его разработка прекращена. Я не уверен в этом, но, глядя на журнал изменений, они утверждают, что сделали его совместимым с Boost 1.37, который действительно старый. Убедитесь, что то, что вы используете, стоит вашего времени!

1

Другие решения