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

问题描述 投票:14回答:2

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

问题

我可以在Unix和Windows系统上运行该应用程序,该系统最多具有48个内核和256g的内存,但是对于大问题,Windows上的计算时间比Linux上高一个数量级。 Windows不仅需要大量内存,而且随着时间的推移,CPU利用率从开始时的25%下降到几个小时后的5%。

观察

  • 整个问题的大型实例的解决时间从数小时到数天不等,并且最多消耗32g的内存(在Unix上)。子问题的解决时间在ms范围内。
  • 我仅需几分钟即可解决的小问题,我不会遇到此问题。
  • Linux开箱即用地使用了两个套接字,而Windows要求我在BIOS中显式激活内存交错,以便应用程序利用两个内核。但是,是否执行此操作不会对总体CPU利用率随时间的下降造成影响。
  • 当我查看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来代替呢?
java multithreading java-native-interface jvm-hotspot numa
2个回答
0
投票

诸如Windows之类的声音在一段时间未被触动后正在将一些内存缓存到pagefile,这就是CPU受到磁盘速度瓶颈的原因

您可以使用Process Explorer对其进行验证,并检查已缓存了多少内存


0
投票

我认为这种性能差异是由于我们的操作系统管理线程。 JVM隐藏了所有操作系统的差异。您可以在许多站点上找到有关它的信息,例如this。但这并不意味着差异消失。

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

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,视窗10

© www.soinside.com 2019 - 2024. All rights reserved.