>Java >java지도 시간 >Java에서 스레드를 특정 CPU에 바인딩하는 방법은 무엇입니까?

Java에서 스레드를 특정 CPU에 바인딩하는 방법은 무엇입니까?

WBOY
WBOY앞으로
2023-05-09 23:58:172100검색

소개

현대 컴퓨터 시스템에는 여러 개의 CPU가 있을 수 있으며 각 CPU에는 여러 개의 코어가 있을 수 있습니다. 최신 CPU의 기능을 최대한 활용하기 위해 JAVA에는 멀티스레딩이 도입되었습니다. 서로 다른 스레드가 서로 다른 CPU 또는 서로 다른 CPU 코어에서 동시에 실행될 수 있습니다. 하지만 JAVA 프로그래머에게는 자신이 생성하는 스레드 수를 제어할 수 있지만 스레드가 어떤 CPU에서 실행되는지는 블랙박스이므로 일반적으로 알기 어렵습니다.

그러나 서로 다른 CPU 코어가 동일한 스레드를 예약하는 경우 CPU 전환으로 인해 성능 저하가 발생할 수 있습니다. 일반적인 상황에서는 이 손실이 상대적으로 적지만, 프로그램이 이러한 종류의 CPU 전환으로 인한 손실을 특히 염려한다면 오늘 이야기할 Java Thread Affinity를 사용해 볼 수 있습니다.

Java Thread Affinity 소개

java 스레드 선호도 JAVA 코드의 스레드를 CPU의 특정 코어에 바인딩하여 프로그램 실행 성능을 향상시키는 데 사용됩니다.

분명히 기본 CPU와 상호 작용하기 위해 Java 스레드 Affinity는 JAVA 및 기본 메서드와 상호 작용하는 방법을 반드시 사용하게 됩니다. JNI가 JAVA 및 기본 메서드와 상호 작용하는 공식적인 방법이지만 JNI는 사용하기 쉽지 않습니다. 오히려 번거롭습니다. 따라서 Java Thread Affinity에서는 실제로 JNA를 사용합니다. JNA는 JNI를 기반으로 개선된 라이브러리로 네이티브 메소드와 상호 작용합니다.

먼저 CPU, CPU 소켓, CPU 코어 등 CPU에 관한 여러 개념을 소개하겠습니다.

첫 번째는 CPU입니다. CPU의 전체 이름은 중앙 처리 장치라고도 하며 작업 처리에 사용되는 핵심 코어입니다.

그럼 CPU소켓이란 무엇일까요? 소위 소켓은 CPU가 삽입되는 슬롯입니다. 데스크톱 컴퓨터를 조립했다면 소켓에 CPU가 설치된다는 것을 알아야 합니다.

CPU 코어는 CPU에 포함된 코어 수를 의미합니다. 옛날에는 CPU가 모두 싱글 코어였습니다. 그러나 멀티 코어 기술이 발전하면서 CPU에는 여러 개의 코어가 포함될 수 있게 되었습니다. 실제로 업무를 수행하는 부서입니다.

Linux 시스템을 사용하는 경우 아래와 같이 lscpu 명령을 사용하여 시스템의 CPU 상태를 확인할 수 있습니다.

Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                1
On-line CPU(s) list:   0
Thread(s) per core:    1
Core(s) per socket:    1
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 94
Model name:            Intel(R) Xeon(R) Gold 6148 CPU @ 2.40GHz
Stepping:              3
CPU MHz:               2400.000
BogoMIPS:              4800.00
Hypervisor vendor:     KVM
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              4096K
L3 cache:              28160K
NUMA node0 CPU(s):     0
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single fsgsbase bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 arat

위 출력에서 ​​이 서버에 소켓이 있고 각 소켓이 있음을 알 수 있습니다. 코어는 1개이며, 각 코어는 동시에 1개의 스레드를 처리할 수 있습니다.

이러한 CPU 정보를 CPU 레이아웃이라고 할 수 있습니다. Linux에서는 CPU 레이아웃 정보가 /proc/cpuinfo에 저장됩니다.

Java Thread Affinity에는 이 정보에 대응하는 CpuLayout 인터페이스가 있습니다.

public interface CpuLayout {
    
    int cpus();
    int sockets();
    int coresPerSocket();
    int threadsPerCore();
    int socketId(int cpuId);
    int coreId(int cpuId);
    int threadId(int cpuId);
}

CPU 레이아웃 정보에 따라 AffinityStrategies는 서로 다른 스레드 간의 배포 관계를 정렬하는 몇 가지 기본 Affinity 전략을 제공하며 주로 다음 유형이 있습니다.

    SAME_CORE - 运行在同一个core中。
    SAME_SOCKET - 运行在同一个socket中,但是不在同一个core上。
    DIFFERENT_SOCKET - 运行在不同的socket中
    DIFFERENT_CORE - 运行在不同的core上
    ANY - 任何情况都可以

이러한 전략은 CpuLayout의 SocketId 및 coreId를 기준으로 구별됩니다. SAME_CORE를 예로 들어 특정 구현을 누릅니다.

SAME_CORE {
        @Override
        public boolean matches(int cpuId, int cpuId2) {
            CpuLayout cpuLayout = AffinityLock.cpuLayout();
            return cpuLayout.socketId(cpuId) == cpuLayout.socketId(cpuId2) &&
                    cpuLayout.coreId(cpuId) == cpuLayout.coreId(cpuId2);
        }
    }

Affinity 전략은 순서대로 있을 수 있습니다. 이전 전략에서는 그렇지 않은 경우 먼저 일치합니다. 두 번째 전략이 선택됩니다.

AffinityLock 사용법

다음으로 Affinity의 구체적인 용도를 살펴보겠습니다. 첫 번째는 CPU 잠금을 얻는 것입니다. JAVA7 이전에는 다음과 같이 작성할 수 있습니다.

AffinityLock al = AffinityLock.acquireLock();
try {
     // do some work locked to a CPU.
} finally {
     al.release();
}

JAVA7 이후에는 다음과 같이 작성할 수 있습니다.

try (AffinityLock al = AffinityLock.acquireLock()) {
    // do some work while locked to a CPU.
}

acquireLock 메서드 스레드에 대해 사용 가능한 모든 CPU를 얻을 수 있습니다. 이것은 거친 자물쇠입니다. 세분화된 코어를 얻으려면 acquireCore:

try (AffinityLock al = AffinityLock.acquireCore()) {
    // do some work while locked to a CPU.
}

acquireLock을 사용할 수 있습니다. 또한 현재 스레드를 획득한 CPU 잠금에 바인딩할지 여부를 나타내는 바인드 매개변수가 있습니다. 바인드 매개변수가 true인 경우 현재 스레드는 acquireLock에서 얻은 CPU에서 실행 중입니다. 바인드 매개변수=false인 경우, acquireLock이 향후 특정 시점에 바인드될 것임을 의미합니다.

위에서 AffinityStrategy를 언급했습니다. 이 AffinityStrategy는 acquireLock의 매개 변수로 사용될 수 있습니다.

public AffinityLock acquireLock(AffinityStrategy... strategies) {
 return acquireLock(false, cpuId, strategies);
    }

현재 AffinityLock의 acquireLock 메서드를 호출하면 현재 스레드에 이전 잠금 전략과 관련된 AffinityLock이 할당될 수 있습니다.

AffinityLock은 CPU와 스레드의 현재 바인딩 상태를 볼 수 있는 dumpLocks 메서드도 제공합니다. 예를 들어 보겠습니다.

private static final ExecutorService ES = Executors.newFixedThreadPool(4,
           new AffinityThreadFactory("bg", SAME_CORE, DIFFERENT_SOCKET, ANY));
for (int i = 0; i < 12; i++)
            ES.submit(new Callable<Void>() {
                @Override
                public Void call() throws InterruptedException {
                    Thread.sleep(100);
                    return null;
                }
            });
        Thread.sleep(200);
        System.out.println("\nThe assignment of CPUs is\n" + AffinityLock.dumpLocks());
        ES.shutdown();
        ES.awaitTermination(1, TimeUnit.SECONDS);

위 코드에서 우리는 4개의 스레드로 구성된 스레드 풀을 생성했으며 해당 ThreadFactory는 AffinityThreadFactory이고 스레드 풀 이름을 bg로 지정하고 3개의 AffinityStrategy를 할당했습니다. 이는 먼저 동일한 코어에 할당한 다음 다른 소켓에 할당하고 마지막으로 사용 가능한 CPU에 할당하는 것을 의미합니다.

그런 다음 특정 실행 프로세스 중에 12개의 스레드를 제출했지만 스레드 풀에는 최대 4개의 스레드만 있습니다. AffinityLock.dumpLocks 메서드에서 반환된 결과에서는 4개의 스레드만 CPU에 바인딩될 것으로 예상됩니다. 살펴보겠습니다.

The assignment of CPUs is
0: CPU not available
1: Reserved for this application
2: Reserved for this application
3: Reserved for this application
4: Thread[bg-4,5,main] alive=true
5: Thread[bg-3,5,main] alive=true
6: Thread[bg-2,5,main] alive=true
7: Thread[bg,5,main] alive=true

출력에서 ​​볼 수 있듯이 CPU0을 사용할 수 없습니다. 다른 7개의 CPU를 사용할 수 있지만 여기에 바인딩된 스레드는 4개뿐이며 이는 이전 분석과 일치합니다.

다음으로 AffinityThreadFactory의 AffinityStrategy를 다음과 같이 수정합니다.

new AffinityThreadFactory("bg", SAME_CORE)

는 스레드가 동일한 코어에만 바인딩된다는 것을 의미합니다. 현재 하드웨어에서는 하나의 코어가 동시에 하나의 스레드 바인딩만 지원할 수 있기 때문입니다. 따라서 최종 결과는 하나의 스레드에만 바인딩될 것으로 예측할 수 있으며 실행 결과는 다음과 같습니다.

The assignment of CPUs is
0: CPU not available
1: Reserved for this application
2: Reserved for this application
3: Reserved for this application
4: Reserved for this application
5: Reserved for this application
6: Reserved for this application
7: Thread[bg,5,main] alive=true

可以看到只有第一个线程绑定了CPU,和之前的分析相匹配。

使用API直接分配CPU

上面我们提到的AffinityLock的acquireLock方法其实还可以接受一个CPU id参数,直接用来获得传入CPU id的lock。这样后续线程就可以在指定的CPU上运行。

    public static AffinityLock acquireLock(int cpuId) {
        return acquireLock(true, cpuId, AffinityStrategies.ANY);
    }

实时上这种Affinity是存放在BitSet中的,BitSet的index就是cpu的id,对应的value就是是否获得锁。

先看下setAffinity方法的定义:

    public static void setAffinity(int cpu) {
        BitSet affinity = new BitSet(Runtime.getRuntime().availableProcessors());
        affinity.set(cpu);
        setAffinity(affinity);
    }

再看下setAffinity的使用:

long currentAffinity = AffinitySupport.getAffinity();
Affinity.setAffinity(1L << 5); // lock to CPU 5.

注意,因为BitSet底层是用Long来进行数据存储的,所以这里的index是bit index,所以我们需要对十进制的CPU index进行转换。

위 내용은 Java에서 스레드를 특정 CPU에 바인딩하는 방법은 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제