我正在开发一个Java应用程序,用于解决一类数值优化问题——更准确地说是大规模线性规划问题。一个问题可以分成多个小子问题并行求解。由于子问题数量超过CPU核心数量,因此我使用ExecutorService,并将每个子问题定义为一个Callable提交给ExecutorService。解决子问题需要调用本地库——在这种情况下是线性规划求解器。
问题
我可以在Unix和Windows系统上运行该应用程序,最多可使用44个物理核心和256g内存,但对于大型问题,Windows的计算时间比Linux高一个数量级。Windows不仅需要更多的内存,而且随着时间的推移,CPU利用率从一开始的25%下降到几个小时后的5%。以下是Windows任务管理器的截图:
观察结果:
- 解决大规模问题的解决方案时间范围从几小时到几天,并且消耗高达32g的内存(在Unix上)。子问题的解决时间在毫秒级别。
- 对于只需要几分钟就能解决的小问题,我不会遇到这个问题。
- Linux默认使用两个套接字,而Windows需要我在BIOS中显式激活内存交错,以便应用程序利用两个核心。无论我是否这样做,都不会对整体CPU利用率的恶化产生影响。
- 当我在VisualVM中查看线程时,所有池线程都在运行,没有一个处于等待状态或其他状态。
- 根据VisualVM的显示,90%的CPU时间花费在本地函数调用上(解决小型线性规划问题)。
- 垃圾收集不是问题,因为应用程序不创建和反引用大量对象。此外,大多数内存似乎是分配在堆外的。对于最大实例,Linux上的4g堆足够,而Windows上则需要8g。
- 各种JVM参数,高XMS,高元空间,UseNUMA标志,其他GC。
- 不同的JVM(Hotspot 8、9、10、11)。
- 不同线性规划求解器的不同本地库(CLP、Xpress、Cplex、Gurobi)。
- 是什么导致了一个大型多线程Java应用程序在Linux和Windows之间的性能差异,而且还会频繁使用本地调用?
- 我能否改变实现中的一些内容来帮助Windows,例如,我应该避免使用接收成千上万个可调用对象的ExecutorService并改用什么?
ForkJoinPool
比手动调度更有效率。 - Karol Dowbecki