Javaの高度な使用法:スレッド-CPUバウンドスレッドのアフィニティ

一緒に書く習慣を身につけましょう!「ナゲッツデイリーニュープラン・4月アップデートチャレンジ」に参加して13日目です。クリックしてイベントの詳細をご覧ください

序章

最近のコンピュータシステムでは、複数のCPUが存在する可能性があり、各CPUは複数のコアを持つことができます。最新のCPUの機能を最大限に活用するために、JAVAはマルチスレッドを導入し、さまざまなスレッドをさまざまなCPUまたはさまざまなCPUコアで同時に実行できます。ただし、JAVAプログラマーの場合、作成されるスレッドの数は自分で制御できますが、スレッドが実行されるCPUはブラックボックスであり、一般に知るのは困難です。

ただし、異なるCPUコアが同じスレッドをスケジュールすると、CPUの切り替えによってパフォーマンスが低下する可能性があります。通常の状況では、この損失は比較的小さいですが、プログラムがこのCPUスイッチングによって引き起こされる損失を特に懸念している場合は、今日お話しするJavaスレッドアフィニティを試すことができます。

Javaスレッドアフィニティの概要

Javaスレッドアフィニティは、JAVAコードのスレッドを特定のCPUコアにバインドして、プログラムのパフォーマンスを向上させるために使用されます。

明らかに、基盤となるCPUと対話するには、JavaスレッドAffinityはJAVAとネイティブメソッド間の対話方法を使用する必要があります。JNIはネイティブメソッドと対話するJAVAの公式メソッドですが、JNIの使用はより面倒です。したがって、JavaスレッドAffinityは実際にはJNAを使用します。これは、ネイティブメソッドと相互作用するJNIに基づく改良されたライブラリです。

まず、CPUのいくつかの概念、つまりCPU、CPUソケット、CPUコアを紹介しましょう。

1つ目は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つのスレッドを処理できることがわかります。

これらのCPUの情報は、CPUレイアウトと呼ぶことができます。Linuxでは、CPUのレイアウト情報は/ proc/cpuinfoに保存されます。

この情報に対応するJavaThreadAffinityには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);
        }
    }
复制代码

アフィニティ戦略を注文することができ、前の戦略が最初に一致し、一致しない場合は2番目の戦略が選択されます。

AffinityLockの使用

次に、Affinityの具体的な使用法を見てみましょう。1つ目は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.
}
复制代码

AccurateLockメソッドは、スレッドで使用可能なCPUを取得できます。これは粗いロックです。きめ細かいコアを取得したい場合は、acquireCoreを使用できます。

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

また、acquireLockには、現在のスレッドを取得したCPUロックにバインドするかどうかを示すbindパラメーターがあります。bindparameter= trueの場合、現在のスレッドは、acquireLockで取得したCPUで実行されます。bindパラメーター=falseの場合、acquireLockは将来のある時点でバインドされることを意味します。

上記のAffinityStrategyについて説明しましたが、このAffinityStrategyはacquireLockのパラメーターとして使用できます。

    public AffinityLock acquireLock(AffinityStrategy... strategies) {
        return acquireLock(false, cpuId, strategies);
    }
复制代码

通过调用当前AffinityLock的acquireLock方法,可以为当前的线程分配和之前的lock策略相关的AffinityLock。

AffinityLock还提供了一个dumpLocks方法,用来查看当前CPU和thread的绑定状态。我们举个例子:

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。 意思是首先分配到同一个core上,然后到不同的socket上,最后是任何可用的CPU。

然后具体执行的过程中,我们提交了12个线程,但是我们的Thread pool最多只有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)
复制代码

表示线程只会绑定到同一个core中,因为在当前的硬件中,一个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を使用しているため、ここでのインデックスはビットインデックスであり、10進数のCPUインデックスを変換する必要があることに注意してください。

要約する

Java Thread Affinityは、プログラム内のThreadが使用するCPUをJAVAコードから制御できます。これは非常に強力で、誰でも使用できます。

この記事はwww.flydean.com/01-java-thr…に含まれています。

最も人気のある解釈、最も深遠な乾物、最も簡潔なチュートリアル、そしてあなたが知らない多くのトリックがあなたが発見するのを待っています!

私の公式アカウントに注意を払うことを歓迎します:「それらをプログラムする」、テクノロジーを理解し、あなたをよりよく理解する!

おすすめ

転載: juejin.im/post/7085981545277685790