【问题标题】:Why is this benchmark code using such high CPU?为什么这个基准代码使用如此高的 CPU?
【发布时间】:2014-01-26 13:00:59
【问题描述】:

下面的代码有效,它发送了所有正确的数据,并且接收了正确的数据。

当我用它来对一个速度非常快的服务器进行基准测试时,基准测试的 CPU 使用率约为 10%。但是,当我对慢速服务器进行基准测试时,它会上升到 ~50%——与我正在基准测试/压力测试的服务器相同*。

top 的报告就是这样。

为什么要使用这么多 CPU?我怀疑我在滥用民意调查,但我不确定如何?

慢速服务器的 CPU 时间是基准的 4 倍,而快速服务器的 CPU 时间是基准的 7 倍。

int flags = fcntl(sockfd, F_GETFL, 0);
assert(flags != -1);
assert(fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) != -1);

int32 red = 0;
struct pollfd pollfd = {
    .fd = sockfd,
    .events = POLLIN | POLLOUT
};
do {
    assert(poll(&pollfd, 1, -1) == 1);
    if (pollfd.revents & POLLOUT) {
        int n;
        while ((n = send(sockfd, buf__+bufOffset, bufLength-bufOffset, MSG_NOSIGNAL)) > 0) {
            bufOffset += n;
            if (n != bufLength-bufOffset)
                break;
        }
        assert(!(n == -1 && errno != EAGAIN && errno != EWOULDBLOCK));
    }

    if (pollfd.revents & POLLIN) {
        int r;
        while ((r = read(sockfd, recvBuf, MIN(recvLength-red, recvBufLength))) > 0) {
            // assert(memcmp(recvBuf, recvExpectedBuf+red, r) == 0);
            red += r;
            if (r != MIN(recvLength-red, recvBufLength))
                break;
        }
        assert(!(r == -1 && errno != EAGAIN && errno != EWOULDBLOCK));
    }
} while (bufOffset < bufLength);

assert(fcntl(sockfd, F_SETFL, flags & ~O_NONBLOCK) != -1);
int r;
while ((r = read(sockfd, recvBuf, MIN(recvLength-red, recvBufLength))) > 0) {
    // assert(memcmp(recvBuf, recvExpectedBuf+red, r) == 0);
    red += r;
}
assert(fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) != -1);

assert(red == recvLength);

int r = read(sockfd, recvBuf, 1);
assert((r == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) || r == 0);

*(我现在在同一台机器上同时运行基准测试和服务器。通信是通过 TCP 进行的。)

【问题讨论】:

  • 我不太明白这个问题...如果你有一台更强大的计算机,很明显它比低端计算机更容易处理大量数据。这可以结束:在“高性能”计算机上更快地完成或“一次”完成但在低性能上使用更多 CPU。
  • @SergiCastellsaguéMillán 这一切都在同一台计算机上,都在同一个 CPU 内核上。 “慢速服务器”和“快速服务器”之间的唯一区别是,一个是庞大的慢速代码库,开销很大,一个是我新的、轻量级的重写。
  • 服务器是否仅在完整消息后才回复?如果是这样...
  • .revents = POLLIN | POLLOUT 你不应该设置它,而不是 do-while 只是让它更好 while(poll(pollfd,...)) 循环。此外,AFAIK POLLIN 意味着您需要阅读,而不是写作,POLLOUT 也是如此。
  • @user9000 我正在设置它,并使用do...while,因为它几乎可以保证在第一次循环中被读/写。

标签: c linux posix poll-syscall


【解决方案1】:

原因是你正忙着等待。如果readwrite 返回EAGAINEWOULDBLOCK,您正在不断地调用它们。添加一个select,它会等到套接字准备好读取或写入之前。

【讨论】:

  • 我不认为我是,while 循环仅在其返回值> 0 时继续,然后在do...while 循环中进行轮询。
  • 正确,但是当 fd 准备好读取或准备好写入时会返回。您需要在每个人之前单独等待。我会使用select,但我想poll 没问题。
  • 我刚刚更新了代码以考虑到这一点,唉,仍然是 50% 的 CPU 使用率。
【解决方案2】:

所以如果我终于明白了,你是在比较top 报告的%CPU 的比率与top 报告的TIME+ 的增长率的比率,他们不同意。 (如果您说您正在阅读哪些列,那会更容易!)据我所知,两者都是根据基础/proc 数据中的相同字段计算的,因此他们不可能不同意很多。

而且我无法复制它。我已将您的代码放入一个测试程序并在没有修改的情况下运行它,除了修复int r 编译错误的重新声明并为您遗漏的所有内容添加我认为合理的声明。我将它连接到一个服务器,该服务器从客户端读取线路,并在每条线路之后消耗一点 CPU,然后再发送一条线路。结果是顶部显示%CPU 大约为服务器端的 99 和客户端的 2 和大约 50 比 1 的比率在 TIME+ 列中。

我觉得使用poll没有任何问题。

虽然我不喜欢你使用assert - 当断言被关闭时,程序会丢失很多重要的系统调用。

【讨论】:

  • 感谢您的帮助,Wumpus。我使用 epoll 重写了它,只是遇到了同样的问题。然后我开始计时每个部分花费了多长时间,并意识到它对do...while 循环的迭代比我想象的要多。慢速服务器输出 8 字节包,每一个我都收到通知,每一个我都调用 read(2)。读取的系统开销是导致 CPU 时间高的原因。
【解决方案3】:

问题解决了。

这并没有准确地歪曲 CPU 使用率。效率低下的服务器使用 TCP_NODELAY 发送 8 字节数据包,因此我收到了数百万条轮询通知,只读取 8 个字节。事实证明 read(2) 调用相当昂贵,每秒调用数万次足以看到“在系统模式下花费的时间”飙升至约 56%,这被添加到“在用户模式下花费的时间” " 以产生非常高的 CPU 使用率。

【讨论】:

  • 完全废话。如果您的进程在poll 中被阻止,则它只会被视为空闲。
  • @WumpusQ.Wumbley 是什么让我得出结论,我可以同时运行 10 个基准测试实例,并且它们报告的 CPU 使用率都下降了。因此,服务器将消耗 ~50%,每个基准测试将消耗 ~5%。除了大致上述之外,我无法理解任何可以解释这一点的解释。
  • 由于您在同一台机器上运行客户端和服务器,因此没有网络延迟。如果服务器除了写入客户端之外没有阻塞任何任务,则没有理由期望总 CPU 使用率低于 100%。如果服务器不需要大量 CPU 来生成它发送给客户端的响应,那么没有理由不期望服务器分配 50% 和客户端之间分配 50%。那么您做了什么,为什么?
  • @WumpusQ.Wumbley 服务器的工作量比客户端多一个数量级。高效编写的服务器显示的 CPU 时间是客户端的 7 倍。编写效率低的服务器显示的 CPU 时间是客户端的 4 倍(尽管服务器和客户端在 top 中都显示了约 50%)。考虑到编写效率低的服务器的开销,我预计会接近 70 倍。
  • 你的 7x 和 4x 数字从何而来?
猜你喜欢
  • 1970-01-01
  • 2019-05-10
  • 2019-12-19
  • 1970-01-01
  • 2015-06-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-15
相关资源
最近更新 更多