CSocket::Send 是不是存在性能问题?

Posted

技术标签:

【中文标题】CSocket::Send 是不是存在性能问题?【英文标题】:Are there performance issues with CSocket::Send?CSocket::Send 是否存在性能问题? 【发布时间】:2011-06-10 17:27:33 【问题描述】:

2011 年 6 月 14 日更新

快速更新... 大多数受访者都专注于处理要记录的消息队列的狡猾方法,但是虽然肯定缺乏优化,但这肯定不是问题的根源。我们将 Yield 切换为短暂睡眠(是的,一旦系统安静下来,Yield 确实会导致 100% 的 CPU)但是系统仍然无法跟上日志记录,即使它远未达到睡眠状态。据我所知,发送并不是很有效。一位受访者评论说,我们应该将 Send() 一起阻塞到一个发送中,这似乎是解决更大的潜在问题的最合适的解决方案,这就是为什么我将其标记为原始问题的答案。我当然同意队列模型存在很大缺陷,因此感谢您对此的反馈,并且我对所有有助于讨论的答案都投了赞成票。

然而,这个练习让我们回顾了为什么我们像我们一样在套接字上使用外部日志记录,虽然以前当日志记录服务器做了很多处理日志条目......它不再做任何事情,因此我们选择远程整个模块并通过一些预先存在的日志框架采用直接到文件的方法,这应该完全消除问题,因为并消除系统中不必要的复杂性。

再次感谢所有反馈。

原始问题

在我们的系统中,我们有两个对这个问题很重要的组件 - 一个是用 Visual C++ 开发的,另一个是 Java (不要问,历史原因)。

C++ 组件是主要服务并生成日志条目。这些日志条目通过 CSocket::Send 发送到 Java 日志服务。

问题

发送数据的性能似乎很低。如果我们在 C++ 端排队,那么队列会在更繁忙的系统上逐步备份。

如果我用一个简单的 C# 应用程序访问 Java Logging Server,那么我可以更快地敲击它,然后我将永远需要从 C++ 工具中获得它,并且它可以很好地跟上。

在 C++ 世界中,将消息添加到队列中的函数是:

void MyLogger::Log(const CString& buffer)

    struct _timeb timebuffer;
    _ftime64_s( &timebuffer );

    CString message;
    message.Format("%d%03d,%04d,%s\r\n", (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 的一个限制,使它对我们没有用处吗?滥用它?还是整个红鲱鱼,问题出在其他地方?

非常感谢您的建议。

亲切的问候

马特·佩德尔斯登

【问题讨论】:

尝试注释掉 Yield()。 如果我注释掉产量,系统将旋转到 100% CPU。我会认为如果队列中有东西,那么 try_pop 会返回,它甚至不会到达 Yield?还是我错过了什么……? 你正忙于旋转,如果队列中没有项目,try_pop 不会阻塞。无论有没有使用 yield() 调用,当前的方案似乎都非常无效。如果队列为空,您将需要以某种方式阻塞,并在数据被推送到队列时唤醒。不过,concurrent_queue 似乎没有提供这一点,因此您必须自己制作一些东西,无论是使用条件变量还是事件。 好的,我会接受反馈,我们会调查它 - 但是,如果系统现在大部分空闲(超时),并且日志大约 1 小时过期(大约 150mb 的内存)——为什么现在不能很快地把它炸掉?我假设您描述的情况意味着我们在白天落后,但一旦系统安静下来就会很快赶上。碰巧的是,系统正在以与一整天相同的速度进行记录。在这种情况下,我们每次都从 try_pop 中获取一些东西,并将其记录在线程的每个周期中,大概是这样吗?还是我错过了什么? 什么是“线程循环”?如果这个函数在其他线程循环中被连续调用,那么我希望这个函数和调用它的循环在系统其余部分空闲时用完 100% CPU,无论 Yield() 是在还是不是。调用 Yield() 只会立即重新安排该线程,因为没有其他事情可做。这非常令人困惑! 【参考方案1】:

好吧,您可以从使用阻塞的生产者-消费者队列开始并摆脱“产量”。我对消息被阻塞并不感到惊讶——当一个消息被发布时,记录器线程通常在一个繁忙的系统上准备好但没有运行。在处理队列上的任何消息之前,这将引入很多可避免的延迟。后台线程比有一个量子来尝试摆脱队列中累积的所有消息。如果在繁忙的系统上有很多就绪线程,很可能是线程没有足够的时间来处理消息。特别是如果已经建立了很多并且 socket.send 阻塞了。

此外,在队列轮询上几乎完全浪费一个 CPU 内核对整体性能没有好处。

Rgds, 马丁

【讨论】:

我的理解是,产量意味着我们没有使用过多的 CPU——事实上,此时在一个实时服务器上,当前正在记录 1 小时以上的实时服务器,CPU 使用率约为 0% 并且我一整天都没有看到任何异常的 CPU 使用率。日志以与它们一整天相同的速度稳定地流出到日志服务器。因此,在这种情况下,甚至从未达到收益率。我必须承认,我们对这个实现的意图是尝试消除所有锁定 - 但我们当然可以尝试切换到使用等待/通知的实现。 哦!等等……那么什么叫 ProcessQueue?是否有另一个循环带有一些你没有显示的信号? 它在原始问题的第三部分代码中 - 基本上,在运行时,logger->processqueue。 好的,所以我得到 5% 用于观察 您可能希望避免锁定,但考虑到 concurrent_queue,那里可能已经有锁定(不确定您的 c_c 实现是否是无锁的)。作为第一次尝试,添加一个信号量,初始化为零。在将消息推送到队列后发出信号,并在线程中等待它,然后再调用 try-wait。【参考方案2】:

在我看来,您肯定不是在寻找最有效的解决方案。你绝对应该调用一次 Send() 。对于所有消息。连接用户端队列中的所有消息,使用 Send() 一次性发送,然后 yield。

此外,这确实不是您应该这样做的方式。 PPL 包含显式用于异步回调的构造,例如 call 对象。您应该使用它而不是自己手动滚动。

【讨论】:

啊,所以不是每行日志使用一个 Send(),我们可以将它们全部连接到一个大 CString 中并使用单个 Send()?我喜欢这个主意......发送限制呢?或者这是否需要重复调​​用 Send() 直到所有字节都消失(如一些在线示例中所述)? 另外,我不完全确定您所说的 PPL 是什么意思 - 如果这是一个基本问题,抱歉!您也可以扩展答案的这方面吗?非常感谢。【参考方案3】:

这里可能会拖慢您的速度:

您正在使用的队列。我认为这是过早优化的经典示例。这里没有理由使用 Concurrency::concurrent_queue 类而不是具有阻塞 pop() 方法的常规消息队列。如果我理解正确,Concurrency 类使用非阻塞算法,在这种情况下,您确实想在队列为空时阻塞并释放 CPU 供其他线程使用。 对每条消息以及 CString 类的内部分配使用 new 和 delete。 您应该尝试看看回收消息和字符串(使用池)是否有助于提高性能,原因有两个:1。消息和字符串对象的分配和释放。 2. 如果字符串类将在内部回收其缓冲区,则可以避免在字符串内部进行的分配和释放。

【讨论】:

【参考方案4】:

您是否尝试过分析以查看您的应用程序在哪里出现问题?是否仅在记录发件人时出现问题?是 CPU 受限还是阻塞?

我唯一能看到的是,您没有使用任何类型的锁定来保护消息队列,因此容器状态可能会变得很奇怪,从而导致各种意外行为。

【讨论】:

消息队列是线程安全的ConcurrentQueue。请参阅 concurrent_queue.h 系统完全不受 CPU 限制,没有。 向众多cmets道歉,在使用该网站时遇到了湿件问题! :) - 还没有尝试分析,遗憾的是我不是 C++ 程序员,所以这对我来说完全是陌生的领域。发送方仅在这种情况下用于日志记录,我们在系统上使用的所有其他套接字的带宽都要低得多,因此我们在其他任何地方都没有看到任何类似的问题。

以上是关于CSocket::Send 是不是存在性能问题?的主要内容,如果未能解决你的问题,请参考以下文章

django如何实现存在性检查

内部联接是不是存在任何性能问题?

在您的 html 中包含大量脚本标签是不是存在任何性能问题?

在其他函数或循环中构造 lambda 时是不是存在性能问题?

WebApp 中的静态 ThreadLocal 变量 - 是不是存在任何安全/性能问题?

SVG 属性和样式之间是不是存在性能差异?