【问题标题】:How to measure and fix context switching bottlenecks?如何测量和修复上下文切换瓶颈?
【发布时间】:2014-07-13 05:47:36
【问题描述】:

我有一个多线程套接字程序。我使用 boost 线程池 (http://threadpool.sourceforge.net/) 来执行任务。我在线程池中为每个线程创建一个 TCP 客户端套接字。每当我发送大量数据(例如 500KB(消息大小))时,吞吐量都会显着降低。我检查了我的代码:

1) 可能导致上下文切换的等待 2) 锁/互斥体

例如,一条 500KB 的消息被分成多行,我使用 ::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 );
}

发送数据:

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,valgrind 中的 callgrind)。 Linux 上的任何工具都可以帮助测量这些瓶颈吗?

【问题讨论】:

  • 您为什么认为上下文切换和/或锁/互斥锁是问题所在?这500KB,是每秒吗?是从一个客户端加载还是从多个客户端聚合加载?另外,没有代码:(
  • > Linux 上的任何工具都可以帮助测量这些瓶颈吗? perf record -e cs -g -p PIDperf.wiki.kernel.org/index.php/…。但是,您提供的信息很少,无法确保上下文切换对此负责。
  • @MartinJames 我已经更新了问题以使其更加清晰。我无法共享代码,因为它跨越多个文件。
  • 啊哈.... '我使用 ::send( ) 通过套接字发送每一行' 现在我们到了某个地方。
  • 请不要告诉我们您一次发送一行并等待应用级确认......

标签: c++ linux multithreading sockets context-switch


【解决方案1】:

一般来说,与网络无关,您需要一个线程来处理每个可以并行使用的资源。换句话说,如果你有一个网络接口,一个线程就足以为网络接口提供服务。由于您通常不仅接收或发送数据,而且还对其进行处理,因此您的线程随后会切换到使用不同的资源,例如用于计算的 CPU 或用于存储或检索的硬盘的 IO 通道。然后,此任务需要在不同的线程中完成,而单个网络线程不断从网络中检索消息。

因此,您为每个连接创建线程的方法似乎是一种保持事物清洁和分离的简单方法,但它根本无法扩展,因为它涉及太多不必要的上下文切换。相反,如果可以的话,将网络保持在一个地方。另外,不要重新发明轮子。有一些工具,例如zeromq 服务于多个连接,从零散的网络数据包组装整个消息,并且仅在完全接收到一条消息时才调用回调。而且它的性能非常好,所以我建议使用这个工具作为你沟通的基础。此外,它还提供了大量的语言绑定,因此您可以使用脚本语言快速制作节点原型,并在稍后切换到 C++ 以获得性能。

最后,恐怕您正在使用的库(不是似乎是 Boost 的一部分!)是废弃软件,即它的开发已停止。我不确定这一点,但查看更新日志,他们声称他们已使其与 Boost 1.37 兼容,而 Boost 1.37 确实很旧。确保您正在使用的东西值得您花时间!

【讨论】:

  • 好吧,公平地说,您可以将第二段的开头重写为“您正在避免将内联服务器代码编写为复杂的、事件驱动的状态机,方法是使用每个连接的线程。从在资源上阻塞的任何线程中移除 CPU 并将 CPU 提供给另一个可以运行的线程需要上下文切换,这在这种设计中是固有的必要性。如果你确实需要处理大量的连接,你应该考虑将你的服务器重写为一堆乱七八糟的回调。
猜你喜欢
  • 2015-09-07
  • 1970-01-01
  • 1970-01-01
  • 2017-07-13
  • 1970-01-01
  • 1970-01-01
  • 2016-07-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多