Windows上的多线程Java应用程序的CPU使用率太低


18

我正在开发一个Java应用程序,用于解决一类数值优化问题-更确切地说是大规模线性编程问题。单个问题可以分解为多个较小的子问题,这些子问题可以并行解决。由于子问题多于CPU内核,因此我使用ExecutorService并将每个子问题定义为可提交给ExecutorService的Callable。解决子问题需要调用本机库-在这种情况下为线性编程求解器。

问题

我可以在Unix和具有多达44个物理核心和256g内存的Windows系统上运行该应用程序,但是在Windows上,大问题的计算时间比Linux上高一个数量级。Windows不仅需要大量内存,而且随着时间的推移,CPU利用率从开始时的25%下降到几个小时后的5%。这是Windows中任务管理器的屏幕截图:

任务管理器CPU利用率

观察结果

  • 整个问题的大型实例的解决时间从数小时到数天不等,并且最多消耗32g的内存(在Unix上)。子问题的解决时间在ms范围内。
  • 对于仅需几分钟即可解决的小问题,我不会遇到此问题。
  • Linux开箱即用地使用了两个套接字,而Windows要求我显式地激活BIOS中的内存交错,以便应用程序利用两个内核。但是,是否执行此操作不会对总体CPU利用率随时间的下降造成影响。
  • 当我查看VisualVM中的线程时,所有池线程都在运行,没有一个正在等待。
  • 根据VisualVM,90%的CPU时间花在了本机函数调用上(解决了一个小的线性程序)
  • 垃圾回收不是问题,因为该应用程序不会创建和取消引用很多对象。而且,大多数内存似乎是堆外分配的。对于最大实例,Linux上4g的堆就足够了,而Windows上8g的堆就足够了。

我尝试过的

  • 各种JVM arg,高XMS,高元空间,UseNUMA标志和其他GC。
  • 不同的JVM(热点8、9、10、11)。
  • 不同线性编程求解器(CLP,Xpress,Cplex,Gurobi)的不同本机库。

问题

  • 是什么导致大量使用本地调用的大型多线程Java应用程序在Linux和Windows之间的性能差异?
  • 在实现方面有什么可以改变的,例如Windows,我是否应该避免使用接收数千个Callable的ExecutorService来代替呢?

您尝试了ForkJoinPool替代ExecutorService吗?如果您的问题受CPU限制,则25%的CPU利用率确实很低。
卡罗尔·道贝克

1
您的问题听起来像是应该将CPU提升到100%,但您使用的却是25%。对于某些问题,ForkJoinPool比手动计划更为有效。
卡罗尔·道贝克

2
循环浏览热点版本时,是否确定使用的是“服务器”版本而不是“客户端”版本?您在Linux上的CPU使用率是多少?此外,几天的Windows正常运行时间令人印象深刻!你的秘密是什么?:P
erickson

3
也许尝试使用Xperf生成FlameGraph。这可以使您了解CPU的功能(希望是用户模式和内核模式),但是我从来没有在Windows上做到过。
卡罗尔·道贝克

1
@Nils,两次运行(unix / win)都使用相同的接口来调用本地库?我问,因为它看起来很不一样。像:win使用jna,linux jni。
SR

Answers:


2

对于Windows,每个进程的线程数受进程的地址空间限制(另请参见Mark Russinovich-推动Windows的极限:进程和线程)。当它接近极限时,认为这会引起副作用(上下文切换速度变慢,碎片化...)。对于Windows,我会尝试将工作负载划分为一组进程。对于我几年前遇到的类似问题,我实现了Java库来更方便地执行此操作(Java 8),请看一下是否喜欢:库以在外部进程中生成任务


这看起来很有趣!我有点犹豫,但有两个原因:1)通过套接字序列化和发送对象会产生性能开销;2)如果我想序列化所有内容,包括在任务中链接的所有依赖关系-重写代码将需要一些工作-尽管如此,谢谢您的有用链接。
尼尔斯

我完全同意您的担心,并且重新设计代码将是您的努力。在遍历图形时,需要在将工作拆分为新的子进程时引入线程数量的阈值。为了解决2),请查看Java内存映射文件(java.nio.MappedByteBuffer),您可以在进程之间有效共享数据,例如图形数据。Godspeed :)
杰里

0

听起来好像Windows在一段时间未修改页面文件后正在将一些内存缓存到页面文件中,这就是CPU磁盘速度瓶颈的原因

您可以使用Process Explorer验证它并检查已缓存多少内存


您认为?有足够的可用内存。Windows为什么会开始交换?无论如何,谢谢。
尼尔斯

至少在我的笔记本电脑上,即使有足够的内存,有时也会交换最小化的应用程序
犹太人

0

我认为这种性能差异是由于操作系统如何管理线程。JVM隐藏了所有操作系统的差异。有许多网站,你可以读到它,像这样,例如。但这并不意味着差异消失。

我想您正在Java 8+ JVM上运行。由于这个事实,建议您尝试使用流和功能编程功能。当您遇到许多小的独立问题并且希望轻松地从顺序执行转换为并行执行时,函数式编程非常有用。好消息是,您不必定义策略来确定必须管理多少个线程(例如使用ExecutorService)。例如(从此处获取):

package com.mkyong.java8;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class ParallelExample4 {

    public static void main(String[] args) {

        long count = Stream.iterate(0, n -> n + 1)
                .limit(1_000_000)
                //.parallel()   with this 23s, without this 1m 10s
                .filter(ParallelExample4::isPrime)
                .peek(x -> System.out.format("%s\t", x))
                .count();

        System.out.println("\nTotal: " + count);

    }

    public static boolean isPrime(int number) {
        if (number <= 1) return false;
        return !IntStream.rangeClosed(2, number / 2).anyMatch(i -> number % i == 0);
    }

}

结果:

对于普通流,它需要1分钟10秒。对于并行流,需要23秒。PS已通过i7-7700、16G RAM,Windows 10测试

因此,我建议您阅读Java中的函数编程,流,lambda函数,并尝试对您的代码进行少量测试(适用于这种新环境)。


我在软件的其他部分中使用流,但是在这种情况下,遍历图形时会创建任务。我不知道如何使用流包装它。
尼尔斯

您可以遍历图形,建立列表然后使用流吗?
xcesco

并行流只是ForkJoinPool的语法糖。我已经尝试过(请参阅上面的@KarolDowbecki评论)。
尼尔斯

0

您可以发布系统统计信息吗?如果这是唯一可用的工具,那么任务管理器足以提供一些线索。它可以轻松判断您的任务是否正在等待IO-根据您的描述,这听起来像是罪魁祸首。这可能是由于某些内存管理问题引起的,或者是库可能将一些临时数据写入磁盘等。

当您说25%的CPU利用率时,您是说只有几个内核同时忙于工作吗?(可能所有的内核会不时地工作,而不是同时工作。)您是否检查系统中确实创建了多少个线程(或进程)?这个数目是否总是大于核心数目?

如果有足够的线程,它们中的许多线程是否空闲以等待某事?如果为true,则可以尝试中断(或附加调试器)以查看它们在等待什么。


我已经添加了任务管理器的屏幕快照,用于代表该问题的执行。应用程序本身创建的线程与计算机上物理内核的数量一样多。Java为该数字贡献了50多个线程。如前所述,VisualVM表示所有线程都处于繁忙状态(绿色)。他们只是不将CPU推到Windows的极限。他们在Linux上运行。
尼尔斯

@Nils我怀疑你真的不都忙着线程相同的时间,但实际上只有9 -他们的10。它们是在所有内核中随机调度的,因此您的平均利用率为9/44 = 20%。您可以直接使用Java线程而不是ExecutorService来查看区别吗?创建44个线程并不难,每个线程都从任务池/队列中获取Runnable / Callable。(尽管VisualVM显示所有Java线程正忙,但现实情况是44个线程被快速调度,因此所有线程都有机会在VisualVM的采样期间运行。)
Xiao-Feng Li

这是我的想法,也是我实际上在某些时候所做的事情。在我的实现中,我还确保本地访问对于每个线程都是本地的,但这一点都没有区别。
尼尔斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.