Windows에서 멀티 스레드 Java 애플리케이션의 CPU 사용량이 너무 적음


18

대규모 선형 프로그래밍 문제를보다 정확하게하기 위해 수치 최적화 문제 클래스를 해결하기 위해 Java 응용 프로그램을 개발 중입니다. 단일 문제는 더 작은 하위 문제로 나뉘어 병렬로 해결할 수 있습니다. CPU 코어보다 하위 문제가 더 많으므로 ExecutorService를 사용하고 각 하위 문제를 ExecutorService에 제출되는 Callable로 정의합니다. 하위 문제를 해결하려면이 경우 선형 프로그래밍 솔버 인 기본 라이브러리를 호출해야합니다.

문제

최대 44 개의 물리적 코어와 최대 256g 메모리를 가진 Unix 및 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 인수, 높은 XMS, 높은 메타 스페이스, UseNUMA 플래그, 기타 GC
  • 다른 JVM (Hotspot 8, 9, 10, 11).
  • 다른 선형 프로그래밍 솔버 (CLP, Xpress, Cplex, Gurobi)의 서로 다른 기본 라이브러리.

질문

  • 네이티브 호출을 많이 사용하는 대규모 멀티 스레드 Java 애플리케이션의 Linux와 Windows 간의 성능 차이는 무엇입니까?
  • 예를 들어 수천 개의 Callable을 수신하는 ExecutorService를 사용하지 않고 대신 수행해야하는 경우 Windows 구현에 도움이되는 변경 사항이 있습니까?

ForkJoinPool대신 사용해 보셨습니까 ExecutorService? 문제가 CPU 바운드 인 경우 25 % CPU 사용률이 실제로 낮습니다.
Karol Dowbecki

1
문제는 CPU를 100 %로 밀어야하지만 25 %에 달하는 것 같습니다. 일부 문제의 ForkJoinPool경우 수동 예약보다 효율적입니다.
Karol Dowbecki

2
핫스팟 버전을 순환하면서 "클라이언트"버전이 아닌 "서버"를 사용하고 있는지 확인 했습니까? Linux에서 CPU 사용률은 얼마입니까? 또한 며칠의 Windows 가동 시간이 인상적입니다! 당신의 비밀은 무엇입니까? : P
erickson

3
아마 사용해보십시오 Xperf을 생성 FlameGraph을 . 이것은 CPU가 무엇을하고 있는지 (사용자와 커널 모드 모두) 통찰력을 줄 수 있지만 Windows에서는 결코하지 않았습니다.
Karol Dowbecki

1
@Nils, 두 실행 (유닉스 / 윈)은 동일한 인터페이스를 사용하여 기본 라이브러리를 호출합니까? 다른 것처럼 보이기 때문에 묻습니다. : win은 jna, linux jni를 사용합니다.
SR

답변:


2

Windows의 경우 프로세스 당 스레드 수는 프로세스의 주소 공간에 의해 제한됩니다 ( Mark Russinovich-Windows 제한 푸시 : 프로세스 및 스레드 참조 ). 한계에 가까워지면 부작용이 발생한다고 생각하십시오 (컨텍스트 전환 속도 저하, 조각화 ...). Windows의 경우 작업로드를 프로세스 세트로 나누려고합니다. 몇 년 전과 비슷한 문제를 위해 Java 라이브러리를 구현하여이를보다 편리하게 수행 (Java 8) 원하는 경우 살펴보십시오 : 외부 프로세스에서 작업을 생성하는 라이브러리 .


이것은 매우 흥미로운 것 같습니다! 나는 두 가지 이유로 아직 멀어지기를 주저합니다. 1) 소켓을 통해 객체를 직렬화하고 보내는 데 성능 오버 헤드가 있습니다. 2) 모든 것을 직렬화하려면 여기에 작업에 연결된 모든 종속성이 포함됩니다-코드를 다시 작성하는 것이 약간의 작업 일 것입니다. 그럼에도 불구하고 유용한 링크에 감사드립니다.
Nils

귀하의 우려 사항을 전적으로 공유하고 코드를 다시 디자인하는 것이 약간의 노력입니다. 그래프를 탐색하는 동안 작업을 새로운 하위 프로세스로 분할 할 때 스레드 수에 대한 임계 값을 도입해야합니다. 2)를 해결하기 위해 Java 메모리 매핑 파일 (java.nio.MappedByteBuffer)을 살펴보면 프로세스간에 데이터 (예 : 그래프 데이터)를 효과적으로 공유 할 수 있습니다. Godspeed :)
geri

0

Windows가 한동안 손대지 않은 후 일부 메모리를 페이지 파일에 캐싱하는 것처럼 들리므로 CPU가 디스크 속도에 의해 병목 현상을 일으 킵니다.

프로세스 탐색기로이를 확인하고 캐시 된 메모리 양을 확인할 수 있습니다


당신은 생각합니까? 사용 가능한 메모리가 충분합니다. 왜 Windows가 스왑을 시작합니까? 어쨌든 고마워
Nils

메모리가 충분한 경우에도 랩톱 창에서 최소한 최소화 된 응용 프로그램을 교체하고 있습니다.
Jew

0

이 성능 차이는 OS가 스레드를 관리하는 방법 때문이라고 생각합니다. JVM은 모든 OS 차이를 숨 깁니다. 당신이 좋아하는, 그것에 대해 읽을 수 많은 사이트가 있습니다 예는. 그러나 차이가 사라진다는 의미는 아닙니다.

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에서 함수 프로그래밍, 스트림, 람다 함수에 대해 읽고 코드로 소수의 테스트를 구현해보십시오 (이 새로운 컨텍스트에서 작동하도록 적응).


소프트웨어의 다른 부분에서 스트림을 사용하지만이 경우 그래프를 탐색하는 동안 작업이 생성됩니다. 스트림을 사용하여 이것을 감싸는 방법을 모르겠습니다.
Nils

그래프를 탐색하고 목록을 작성한 다음 스트림을 사용할 수 있습니까?
xcesco

병렬 스트림은 ForkJoinPool의 구문 설탕 일뿐입니다. 내가 시도한 것 (위의 @KarolDowbecki 의견 참조).
Nils

0

시스템 통계를 게시 하시겠습니까? 작업 관리자가 사용 가능한 유일한 도구 인 경우 실마리를 제공 할 수 있습니다. 작업이 IO를 기다리고 있는지 쉽게 알 수 있습니다. 설명한 내용에 따라 범인처럼 들립니다. 특정 메모리 관리 문제 때문일 수도 있고 라이브러리가 임시 데이터를 디스크 등에 쓸 수도 있습니다.

CPU 사용률의 25 %를 말할 때 동시에 몇 개의 코어 만 사용 중임을 의미합니까? (모든 코어가 때때로 작동하지만 동시에 작동하지는 않을 수 있습니다.) 시스템에서 실제로 얼마나 많은 스레드 (또는 프로세스)가 생성되는지 확인 하시겠습니까? 숫자가 항상 코어 수보다 큽니까?

스레드가 충분하면 많은 스레드가 유휴 상태입니까? true 인 경우 대기중인 것을 확인하기 위해 인터럽트를 시도하거나 디버거를 연결할 수 있습니다.


이 문제를 나타내는 실행을 위해 작업 관리자의 스크린 샷을 추가했습니다. 응용 프로그램 자체는 머신에 물리적 코어가있는 수만큼 스레드를 작성합니다. Java는이 수치에 50 개가 넘는 스레드를 제공합니다. 이미 말했듯이 VisualVM은 모든 스레드가 사용 중입니다 (녹색). 그들은 단지 CPU를 Windows의 한계로 밀어 넣지 않습니다. 그들은 리눅스에서합니다.
Nils

@ Nils 실제로 모든 스레드가 동시에 바쁘지는 않지만 실제로 9-10 개만 있다고 생각 합니다. 모든 코어에서 무작위로 예약되므로 평균 9/44 = 20 % 활용도가 있습니다. 차이점을보기 위해 ExecutorService 대신 Java 스레드를 직접 사용할 수 있습니까? 44 개의 스레드를 작성하는 것은 어렵지 않고 각각 태스크 풀 / 큐에서 Runnable / Callable을 가져옵니다. (VisualVM은 모든 Java 스레드가 사용 중임을 보여 주지만 실제로는 44 개의 스레드가 신속하게 예약되어 모든 스레드가 VisualVM의 샘플링 기간에서 실행될 기회를 얻도록 할 수 있습니다.)
Xiao-Feng Li

그것은 내가 실제로 한 시점에서 한 생각과 일입니다. 구현에서 네이티브 액세스가 각 스레드에 로컬인지 확인했지만 아무런 차이가 없었습니다.
Nils
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.