>source

나는 수치 최적화 문제를 해결하기 위해 Java 애플리케이션을 작업 중입니다.보다 정확한 대규모 선형 프로그래밍 문제입니다. 하나의 문제를 병렬로 해결할 수있는 작은 하위 문제로 나눌 수 있습니다. CPU 코어보다 하위 문제가 더 많으므로 ExecutorService를 사용하고 각 하위 문제를 ExecutorService에 제출되는 Callable로 정의합니다. 하위 문제를 해결하려면 네이티브 라이브러리 (이 경우 선형 프로그래밍 솔버)를 호출해야합니다.

문제

최대 44 개의 물리적 코어와 최대 256g 메모리가있는 Unix 및 Windows 시스템에서 응용 프로그램을 실행할 수 있지만 Windows의 계산 시간은 큰 문제의 경우 Linux보다 훨씬 더 높습니다. Windows는 훨씬 더 많은 메모리를 필요로 할뿐만 아니라 시간이 지남에 따라 CPU 사용률이 처음 25 %에서 몇 시간 후 5 %로 떨어집니다. 다음은 Windows의 작업 관리자 스크린 샷입니다.

관찰

  • 전체 문제의 대규모 인스턴스에 대한 해결 시간은 몇 시간에서 며칠이며 최대 32g의 메모리를 사용합니다 (Unix에서). 하위 문제의 해결 시간은 ms 범위입니다.
  • 해결하는 데 몇 분 밖에 걸리지 않는 작은 문제에서는이 문제가 발생하지 않습니다.
  • Linux는 기본적으로 두 소켓을 모두 사용하지만 Windows에서는 응용 프로그램이 두 코어를 모두 사용할 수 있도록 BIOS에서 메모리 인터리빙을 명시 적으로 활성화해야합니다. 이렇게하지 않더라도 시간이 지남에 따라 전체 CPU 사용률이 저하되는 데는 영향을 미치지 않습니다.
  • VisualVM의 스레드를 보면 모든 풀 스레드가 실행 중이고 대기중인 스레드가 없습니다.
  • VisualVM에 따르면 CPU 시간의 90 %가 네이티브 함수 호출에 소비됩니다 (작은 선형 프로그램 해결)
  • 가비지 컬렉션은 응용 프로그램이 많은 개체를 만들고 역 참조하지 않기 때문에 문제가되지 않습니다. 또한 대부분의 메모리는 힙에서 할당되는 것 같습니다. 가장 큰 인스턴스의 경우 Linux에서는 4g, Windows에서는 8g이면 충분합니다.

내가 시도한 것

  • 모든 종류의 JVM 인수, 높은 XMS, 높은 메타 스페이스, UseNUMA 플래그, 기타 GC.
  • 다른 JVM (핫스팟 8, 9, 10, 11).
  • 다른 선형 프로그래밍 솔버 (CLP, Xpress, Cplex, Gurobi)의 다양한 기본 라이브러리.

질문

  • 기본 호출을 많이 사용하는 대규모 다중 스레드 Java 애플리케이션에서 Linux와 Windows 간의 성능 차이를 일으키는 요인은 무엇입니까?
  • 예를 들어 수천 개의 콜 러블을 수신하고 대신 무엇을하는 ExecutorService를 사용하지 않아야하는 등 Windows에 도움이되는 구현에서 변경할 수있는 사항이 있습니까?

ExecutorService 대신 ForkJoinPool을 사용해 보셨습니까? 문제가 CPU 바운드 인 경우 25 % CPU 사용률은 매우 낮습니다.

Karol Dowbecki2021-02-24 00:30:08

문제는 CPU를 100 %로 밀어야하는 것처럼 들리지만 25 %에 있습니다. 일부 문제의 경우 ForkJoinPool이 수동 스케줄링보다 더 효율적입니다.

Karol Dowbecki2021-02-24 00:30:08

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

erickson2021-02-24 00:30:08

Xperf를 사용하여 FlameGraph를 생성 해보십시오. 이것은 CPU가하는 일 (사용자 모드와 커널 모드 모두)에 대한 통찰력을 제공 할 수 있지만 Windows에서는 수행하지 않았습니다.

Karol Dowbecki2021-02-24 00:30:08

@Nils, 두 실행 (unix /win) 모두 동일한 인터페이스를 사용하여 네이티브 라이브러리를 호출합니까? 다른 것 같아서 물어 봅니다. 예 : win은 jna, linux jni를 사용합니다.

S.R.2021-02-24 00:44:32
  • 답변 # 1

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

    CPU 사용률의 25 %를 말하는 경우 동시에 작업중인 코어가 몇 개뿐입니까? (모든 코어가 때때로 작동하지만 동시에 작동하지 않을 수 있습니다.) 시스템에서 실제로 생성 된 스레드 (또는 프로세스) 수를 확인 하시겠습니까? 숫자가 항상 코어 수보다 큽니까?

    스레드가 충분하면 많은 스레드가 무언가를 기다리고 있는가? true 인 경우 인터럽트 (또는 디버거 연결)를 시도하여 대기중인 항목을 확인할 수 있습니다.

    이 문제를 대표하는 실행을 위해 작업 관리자의 스크린 샷을 추가했습니다. 애플리케이션 자체는 시스템에 물리적 코어가있는만큼 많은 스레드를 생성합니다. Java는이 수치에 50 개 이상의 스레드를 제공합니다. 이미 말했듯이 VisualVM은 모든 스레드가 사용 중 (녹색)이라고 말합니다. Windows에서 CPU를 한계까지 밀어 붙이지 않습니다. 그들은 Linux에서 수행합니다.

    Nils2021-02-24 00:44:32

    @Nils 나는 당신이 실제로 모든 스레드가 동시에 바쁘지는 않지만 실제로는 9-10 개 뿐이라고 생각합니다. 모든 코어에서 무작위로 예약되므로 평균 사용률이 9/44= 20 %입니다. ExecutorService 대신 Java 스레드를 직접 사용하여 차이점을 확인할 수 있습니까? 44 개의 스레드를 생성하고 각각 태스크 풀 /큐에서 Runnable /Callable을 가져 오는 것은 어렵지 않습니다. (VisualVM은 모든 Java 스레드가 사용중인 것으로 표시하지만 실제로는 44 개의 스레드가 빠르게 예약되어 모든 스레드가 VisualVM의 샘플링 기간에 실행될 수있는 기회를 얻을 수 있습니다.)

    Xiao-Feng Li2021-02-24 00:44:32

    그것은 제가 어떤 시점에서 실제로 한 생각이자 무언가입니다. 내 구현에서 기본 액세스가 각 스레드에 로컬인지 확인했지만 전혀 차이가 없었습니다.

    Nils2021-02-24 00:44:32

  • 답변 # 2

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

    매우 흥미로워 보입니다! 나는 두 가지 이유로 (아직) 여기까지 가기를 조금 망설 인다. 1) 소켓을 통해 객체를 직렬화하고 보내는 성능 오버 헤드가있을 것이다. 2) 작업에 연결된 모든 종속성을 포함하는 모든 것을 직렬화하려면 code를 다시 작성해야합니다. 그럼에도 불구하고 유용한 링크에 감사드립니다.

    Nils2021-02-24 00:44:32

    나는 당신의 우려를 완전히 공유하고 code를 재 설계하는 것이 약간의 노력이 될 것입니다. 그래프를 탐색하는 동안 작업을 새 하위 프로세스로 분할 할 때 스레드 수에 대한 임계 값을 도입해야합니다. 2) 문제를 해결하기 위해 Java 메모리 매핑 파일 (java.nio.MappedByteBuffer)을 살펴보면 그래프 데이터와 같은 프로세스간에 데이터를 효과적으로 공유 할 수 있습니다. Godspeed :)

    geri2021-02-24 00:44:32

  • 답변 # 3

    이 성능 차이는 O.S. 스레드를 관리합니다. 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 초가 걸립니다. P.S는 i7-7700, 16G RAM, 윈도우 10

    그래서 Java의 함수 프로그래밍, 스트림, 람다 함수에 대해 읽고 code로 적은 수의 테스트를 구현해 보는 것이 좋습니다 (이 새로운 컨텍스트에서 작동하도록 조정 됨).

  • 답변 # 4

    Windows가 일정 시간 동안 변경되지 않은 후 일부 메모리를 페이지 파일에 캐싱하는 것처럼 들리므로 CPU가 디스크 속도로 인해 병목 현상이 발생하는 것입니다.

    Process explorer로 확인하고 캐시 된 메모리 양을 확인할 수 있습니다.

  • 이전 jquery : Bootstrap의 팝 오버에 닫기 버튼을 삽입하는 방법
  • 다음 collection_select 도우미에 대한 공백 및 기본값 포함