【发布时间】:2020-03-10 22:58:35
【问题描述】:
我正在开发一个 Java 应用程序,用于解决一类数值优化问题——更准确地说是大规模线性规划问题。单个问题可以拆分为可以并行解决的较小子问题。由于子问题比 CPU 内核多,因此我使用 ExecutorService 并将每个子问题定义为可提交给 ExecutorService 的 Callable。解决子问题需要调用本机库——在这种情况下是线性规划求解器。
问题
我可以在 Unix 和具有多达 44 个物理内核和多达 256g 内存的 Windows 系统上运行该应用程序,但对于大型问题,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(热点 8、9、10、11)。
- 不同线性规划求解器(CLP、Xpress、Cplex、Gurobi)的不同本地库。
问题
- 大量使用本机调用的大型多线程 Java 应用程序导致 Linux 和 Windows 之间的性能差异是什么原因?
- 我可以在实现中进行哪些更改以帮助 Windows,例如,我是否应该避免使用接收数千个 Callables 的 ExecutorService 并改为执行哪些操作?
【问题讨论】:
-
你试过
ForkJoinPool而不是ExecutorService吗?如果您的问题是 CPU 受限,那么 25% 的 CPU 利用率真的很低。 -
您的问题听起来像是应该将 CPU 提高到 100% 的问题,但您却处于 25%。对于一些问题
ForkJoinPool比人工调度效率更高。 -
循环浏览热点版本,您确定您使用的是“服务器”版本而不是“客户端”版本吗?你在 Linux 上的 CPU 利用率是多少?此外,几天的 Windows 正常运行时间令人印象深刻!你的秘密是什么? :P
-
也许可以尝试使用Xperf 生成FlameGraph。这可以让您了解 CPU 在做什么(希望是用户模式和内核模式),但我从未在 Windows 上这样做过。
-
@Nils,两个运行(unix/win)都使用相同的接口来调用本机库?我问,因为它看起来不一样。比如:win用jna,linux jni。
标签: java multithreading java-native-interface jvm-hotspot numa