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

我正在开发一个Java应用程序,用于解决一类数值优化问题 - 更精确的大规模线性规划问题。单个问题可以拆分为可以并行解决的更小的子问题。由于子问题比 CPU 内核多,因此我使用 ExecutorService 并将每个子问题定义为提交到 ExecutorService 的可调用项。求解子问题需要调用本机库 - 在本例中为线性规划求解器。

问题

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

Task Manager CPU utilization

观察

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

我尝试了什么

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

问题

  • 是什么推动了大量使用本机调用的大型多线程 Java 应用程序的 Linux 和 Windows 之间的性能差异?
  • 在实现中,我是否可以更改任何可以帮助Windows的东西,例如,我是否应该避免使用接收数千个可调用的ExecutorService并执行其他操作?

答案 1

对于 Windows,每个进程的线程数受进程地址空间的限制(另请参阅 Mark Russinovich - Push the Limits of Windows: Processes and Threads)。认为这在接近极限时会导致副作用(上下文切换速度减慢,碎片化......对于Windows,我会尝试将工作负载划分为一组进程。对于我多年前遇到的类似问题,我实现了一个Java库来更方便地执行此操作(Java 8),如果您愿意,请查看:在外部进程中生成任务的库


答案 2

听起来像Windows正在将一些内存缓存到页面文件,在它被触及一段时间后,这就是为什么CPU受到磁盘速度的瓶颈

您可以使用进程资源管理器对其进行验证,并检查缓存了多少内存