基准测试CPU使用率:同步与异步HTTP客户端

4

我一直在对我的系统进行一些基准测试,其中一个我更加密切关注的是我编写的程序在一段时间内执行多个HTTP调用时的行为。更具体地说,我有一个微服务,它会根据请求连续发送同步的HTTP调用。为了进行压力测试,我决定将一些负载注入到我的系统中,这将触发微服务执行一个同步的HTTP调用后执行另一个同步的HTTP调用。(每次调用需要约1秒钟返回响应,即阻塞1秒钟)。

事实证明,CPU使用率由程序的这部分主导。虽然其他部分在一段时间内占用5-10%的CPU使用率,但此部分占用了50%以上的CPU使用率,这是巨大的差异和明显的瓶颈。

我的问题是,如果我重新使用异步的HTTP客户端运行此测试,是否会看到类似于我的HTTP调用这样的大量CPU消耗?由于HTTP调用仍然需要1秒钟返回响应,它不会因轮询以检查是否已返回响应而对CPU施加高负载吗?还是它会要求CPU承受更少的负载,因为一个线程可以处理更多工作?CPU使用率会随时间而变化吗?


为什么不先运行这个测试,需要帮助解释结果时再提问呢? - Marco
先进行性能分析如何?您在这里没有说明操作系统、编程语言或任何具体信息。因此,无法给出任何有意义的答案。如果您没有进行性能分析,我可以百分之百确定瓶颈不是您目前所假设的。 - Alois Kraus
2个回答

2
在第一部分中,您提到了:
“我决定通过向系统注入一些负载来进行压力测试,这将触发微服务执行一个同步的HTTP调用。 (每个调用需要约1秒钟返回响应,即阻塞1秒钟)。”
然后,“此部分占据了50%的恒定时间。”
重复的HTTP调用占用更多的CPU时间和线程创建时间
进程背后的CPU使用率
导致CPU使用率高的原因不是轮询,而是上下文切换和线程创建。
当应用程序运行重复的HTTP调用时,它会创建多个线程,就像在网络驱动程序中一样。 每个线程持续约1秒钟才能完成。

Histogram: Process launch dominates over thread launch over context switching etc

每次线程启动和上下文切换都会获得 CPU 时间,如果您连续执行它们,就像运行一个无限循环while(true),但如果它是完全顺序的任务,比如无限循环,它将占用一个 CPU 核心。
异步任务让并行核更忙碌
那么发生了什么?
从脚本中创建线程不是一个顺序任务。它是并行的。
因为您的脚本在一个循环中以顺序方式执行以下操作:
while(true){
    create_http_request();
}

假设这个函数调用会导致系统调用创建一个线程:
function create_network_socket(){
    @syscall.createThread();
}

假设您的脚本create_http_request()调用异步系统调用需要约1微秒。
因此,它可以在1秒内迭代大约一百万次(到线程关闭)。
但是,在每次调用中,脚本都会调用操作系统系统调用以获取作业(创建线程)。
假设这需要5微秒。
因此,以下内容将在一秒钟内发生:
- 您的脚本从一个CPU核心运行一个顺序任务。 - 它要求操作系统提供一百万个作业,这需要4000000微秒的CPU时间才能完成。 - 并且需要5个额外的CPU核心异步执行作业。
这使您拥有6个完全负载的核心。
逆关系瓶颈
HTTP请求脚本和系统调用之间的异步使并行作业成为可能。这就是导致50%的CPU使用率的原因。
运行刺激任务的瓶颈是顺序调用HTTP请求。

因此,它越快,每秒执行的HTTP请求就越多,因此它会消耗更多的CPU来完成其他部分(操作系统)的工作。

所以它似乎具有反向关系瓶颈。

Http异步请求

数据流中发生了什么

当脚本请求HTTP调用时,所发生的事情可以看作是从脚本到目的地的数据流,并返回。我将用一些简单的步骤来说明它,以后再讨论:

script function call
--> framework/lib function call
    --> interpreter/module call
        --> OS socket lib call
            --> OS system call
                --> driver call
                    --> hardware invocation (NIC)
                        --> physical media buffering and signalling 

在这个流程中,您可以考虑一些节点和一些链接。
根据技术的不同,每个链接都可以是半双工或全双工。而每个节点可以使用不同的技术来实现,我们稍后会讨论到它。
但是,在我们的冯·诺伊曼体系结构和运行顺序机器代码的CPU的低级层面上会发生什么?
这些代码将一个接一个地运行,它们能够从数据变化中得到通知的唯一方法有两种:
- 它们检查某个位置:正如您所提到的那样,进行轮询。 - 其他信号中断它们的工作并通知它们。(需要额外的信号链)
当上述任何一个节点(脚本、驱动程序、任何函数调用)需要处理数据时,如果数据还没有准备好,它应该等待直到数据可用,此时使用轮询或中断通知它。
但是,当数据尚未准备好时,它可以表现出两种不同的方式:
- 阻塞:同步。线程等待数据并将CPU传递给操作系统进行另一个进程。 - 非阻塞:异步。线程等待数据。但是,另一个线程开始执行不需要此数据的另一部分。

同步和异步在每个节点中

该流程链中的每个节点都有自己的机制来与链中的其他节点连接。每个单独的节点可以与其他节点同步或异步,每个链接都可以得到响应(使用轮询或中断)。

例如:

即使您对服务器运行同步HTTP请求,您的驱动程序也不会阻塞其他作业。

在某些链接中,可能是一种中断,例如NIC可以从以太网上的两个RX线接收信号,或者它可以像线程轮询IPC管道一样进行轮询。

结论

如果您异步调用HTTP请求,则数据流链中的其他节点将像以前一样操作(大多数情况下是异步的)(取决于程序/技术)。

链接保持不变。

唯一的区别在于您的基准脚本。

如果您的脚本中有另一个Foo作业,则可以执行以下操作:

  • 阻塞:在HTTP响应后
  • 非阻塞:在HTTP请求调用后

您是否有其他作业并不重要。

长期运行

因为基准测试是一个持续1秒的无状态作业循环,所以在1秒后,在请求或响应之后执行 Foo Job 没有任何区别。

1
我试图清理您的格式和英语,但我不知道最后的“长期运行”句子想表达什么意思。 - tripleee
1
你是指优先级反转吗? - tripleee
感谢您的编辑。问题是询问如果使用异步方法实现,是否可以提高性能(CPU使用率)。而且“长时间运行”指的是限制为无限运行。在这种情况下,超过1秒并没有什么区别。 - Abilogos
1
那么像“因为基准测试是一个持续1秒的无状态作业循环,所以没有办法使单个作业运行得更快…”这样的内容? - tripleee

0

异步调用可以将工作委托给操作系统,当然网络是其中之一。 这个来自microsoft.com的链接是关于.net的,但对于任何异步能力框架/语言都是有效的

虽然工作在某些上下文中执行(也就是说,操作系统确实必须将数据传递给设备驱动程序并响应中断),但没有专用线程等待请求返回的数据。这使得系统能够处理更大量的工作,而不是等待某个I/O调用完成。

假设您的微服务运行在这样的语言/框架之上,它肯定会受益于异步调用,因为不会浪费大部分时间处于空闲状态但仍然是内存和CPU成本的线程。

尽管您仍然可能看到大量的CPU消耗。

一次只有一个HTTP调用不能对CPU施加太大压力,因此对您的系统发出多个请求必须触发并行的HTTP请求序列,以便能够占用CPU。如果我们排除代码的效率和没有解析/处理代码的情况,那么实际上消耗CPU的是网络数据的处理。

减轻网络cpu压力的方法是启用tcp卸载和其他可用的“卸载”到网络接口(如果支持)。 tcp offloading wikipedia TCP卸载引擎(TOE)是一种技术,用于网络接口卡(NIC),将整个TCP / IP堆栈的处理卸载到网络控制器。 它主要用于高速网络接口,例如千兆以太网和10千兆以太网,在这些接口中,网络堆栈的处理开销变得显着。
虽然我不会马上假设50%的CPU使用率是明显的瓶颈,但这意味着CPU正在被更多地使用,您必须进行测试和测量。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接