首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Java 8顺序流增加CPU使用率很高

Java 8顺序流增加CPU使用率很高
EN

Stack Overflow用户
提问于 2020-10-21 08:02:35
回答 2查看 1.3K关注 0票数 8

在我的spring引导服务中,我根据订单详细信息和客户详细信息验证收到的订单。

在客户详细信息中,我有不同的对象列表,如服务、属性、产品等,而对于每个列表,我做的事情如下:

代码语言:javascript
复制
products.stream()  
       .filter(Objects::nonNull)  
       .map(Product::getResource)  
       .filter(Objects::nonNull)  
       .filter(<SimplePredicate>)  
       .collect(Collectors.toList());  

我多次使用这样的流来处理产品、服务和属性。我们观察到,性能方面,它提供了非常高的TPS和内存使用也是非常优化的。但是这消耗了大量的CPU。我们正在以Kubernetes吊舱运行这项服务,它占用了所提供CPU的90%。

一个更有趣的观察是,我们给出的CPU越多,实现的TPS就越高,CPU的使用率也达到90%。

是因为流消耗了更多的CPU吗?或者是因为高垃圾收集,因为在每一次流的迭代之后,内部内存可能会被垃圾收集?

编辑-1:

在使用负载测试进行进一步调查后,人们注意到:

  • 每当我们增加并发线程时,由于CPU使用率高,服务开始没有响应,随后CPU突然减少,从而导致TPS降低。
  • 每当我们减少并发线程时,CPU使用率仍然很高,但服务正在以最优的方式执行,即高TPS.

以下是在不同CPU /线程配置下TPS与CPU之间的统计数据。

CPU: 1500米,线程:70

代码语言:javascript
复制
| TPS | 176  | 140 | 125 | 79 | 63 |
|----------------------------------|
| CPU | 1052 | 405 | 201 | 84 | 13 |  

CPU: 1500米,线程:35

代码语言:javascript
复制
| TPS | 500 | 510 | 500 | 530 |
|-----------------------------|
| CPU | 1172| 1349| 1310| 1214|  

CPU: 2500米,线程:70

代码语言:javascript
复制
| TPS |  20 |  20 |  25 |  28 | 26 |
|----------------------------------|
| CPU | 2063| 2429| 2303| 879 | 35 |  

CPU: 2500米,线程:35

代码语言:javascript
复制
| TPS | 1193 | 1200 | 1200 | 1230 |
|---------------------------------|
| CPU | 600  | 1908 | 2044 | 1949 | 

使用的Tomcat配置:

代码语言:javascript
复制
server.tomcat.max-connections=100
server.tomcat.max-threads=100
server.tomcat.min-spare-threads=5

编辑-2:

线程转储分析表明: 80%的http-nio线程处于Waiting on condition状态。这意味着所有线程都在等待什么,没有人在消耗任何CPU来解释CPU使用率低的原因。,但是是什么导致线程等待呢?我也没有在服务中使用任何异步调用,甚至不使用任何并行流,只是上面提到的顺序流。

以下是CPU和TPS关闭时的线程转储:

代码语言:javascript
复制
"http-nio-8090-exec-72" #125 daemon prio=5 os_prio=0 tid=0x00007f014001e800 nid=0x8f waiting on condition [0x00007f0158ae1000]
   java.lang.Thread.State: **TIMED_WAITING** (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x00000000d7470b10> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
    at java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:467)
    at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:89)
    at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:33)
    at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1073)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
    - None
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-11-02 12:57:07

是因为流消耗更多的CPU吗?或者是因为高垃圾收集,因为在每一次流的迭代之后,内部内存可能会被垃圾收集?

显然,流确实会消耗CPU。一般来说,使用非并行流实现的代码确实比使用老式循环实现的代码运行得慢一些。然而,性能上的差异并不大。(可能是5%或10%?)

通常,流不会产生比执行相同计算的老式循环更多的垃圾。例如,如果我们将您的示例与执行相同操作的循环(即生成一个新列表)进行比较,那么我希望这两个版本的内存分配之间存在1比1的对应关系。

简而言之,我不认为流与此直接相关。显然,如果您的服务正在为每个请求处理大量列表(使用流或循环),那么这将影响TPS。更重要的是,如果列表实际上是从后端数据库中获取的。但这也很正常。这可以通过执行请求缓存和调整API请求的粒度来解决,以计算调用方实际上并不需要的昂贵结果。

(我不建议在场景中将parallel()添加到流中。因为您的服务已经被计算(或交换)绑定,所以没有“备用”周期并行运行这些流。在这里使用parallel()可能会减少您的TPS。)

问题的第二部分是关于性能(TPS)与线程计数与(我们认为)VCPU的比较。不可能解释你给出的结果,因为你没有解释测量单位,而且.因为我怀疑还有其他因素在起作用。

然而,作为一般规则:

当应用程序计算密集型时,添加更多的线程是没有帮助的。ergonomic.

  • If

  • 更多的线程意味着更多的内存利用率(线程堆栈+只能从线程堆栈访问的对象)。

  • 更多的内存利用率意味着GC将比物理内存更少地使用

  • 虚拟内存,那么操作系统通常必须从RAM交换页到磁盘和回放。这会影响性能,特别是在垃圾收集过程中.

也有可能有一些影响可以归因于您的云平台。例如,如果您在一个具有大量虚拟服务器的计算节点上运行虚拟服务器,那么您的许多人在每个VCPU上都得不到一个完整的CPU值。如果您的虚拟服务器正在生成大量交换流量,这很可能会进一步减少服务器在CPU资源中的份额。

我们不能说到底是什么导致了您的问题,但是如果我站在您的立场上,我将查看Java日志,并使用操作系统工具(如vmstatiostat )查找过度分页和一般过度I/O的迹象。

票数 1
EN

Stack Overflow用户

发布于 2020-10-31 12:48:56

是因为流消耗更多的CPU吗?

我想你的意思是:消耗的CPU比循环更多?

如果循环和流在做相同的事情,那么似乎没有太大的区别。

视具体情况而定,可能会有细微的差别。以下是关于这个问题的另外两篇文章(有此结果):

https://jaxenter.com/java-performance-tutorial-how-fast-are-the-java-8-streams-118830.html https://dzone.com/articles/java-performance-for-looping-vs-streaming

还是高垃圾收集的原因?因为每次迭代流之后,内部内存可能会被垃圾收集?

根据您的代码片段,此问题无法回答。我不知道某些对象是否不再被引用,所以垃圾收集有一些事情要做。

在这个问题中,我们解释了是什么触发了垃圾收集:

What triggers garbage collection

但是你的问题中没有关于内存使用的信息。

如果您想优化代码,可以选择使用并行流:

代码语言:javascript
复制
products.stream().parallel()  
   .filter(Objects::nonNull)
   ...

您将看到几篇文章(例如,请参阅我的第一个链接),这些文章得出的结论是,在某些情况下,并行流的速度确实更快。所以你可以尝试这个来提高性能。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64459192

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档