現代のコンピューター システムでは、複数の CPU が使用され、各 CPU が複数のコアを持つことができます。最新の CPU の機能を最大限に活用するために、JAVA ではマルチスレッドが導入され、異なる CPU または異なる CPU コアで異なるスレッドを同時に実行できます。しかし、JAVA プログラマーの場合、作成するスレッドの数を制御できますが、スレッドがどの CPU で実行されるかはブラック ボックスであり、一般に知るのは困難です。
ただし、異なる CPU コアが同じスレッドをスケジュールすると、CPU の切り替えによってパフォーマンスが低下する可能性があります。通常の状況では、この損失は比較的小さいですが、プログラムがこの種の CPU 切り替えによって引き起こされる損失を特に懸念している場合は、今日説明する Java スレッド アフィニティを試すことができます。
java スレッド アフィニティは、JAVA コード内のスレッドを CPU の特定のコアにバインドして、プログラムの実行パフォーマンスを向上させるために使用されます。
明らかに、基盤となる CPU と対話する場合、Java スレッド Affinity は対話に JAVA とネイティブ メソッドを使用します。JNI はネイティブ メソッドと対話するための JAVA の公式メソッドですが、JNI は使用するのが比較的面倒です。したがって、Java スレッド Affinity は実際に JNA を使用します (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 に保存されます。
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 は、主にスレッド間のさまざまな分散関係を調整するためのいくつかの基本的なアフィニティ戦略を提供します。
SAME_CORE - 运行在同一个core中。 SAME_SOCKET - 运行在同一个socket中,但是不在同一个core上。 DIFFERENT_SOCKET - 运行在不同的socket中 DIFFERENT_CORE - 运行在不同的core上 ANY - 任何情况都可以
これらの戦略は、CpuLayout のソケット ID とコア ID にも基づいて区別されます。例として 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 番目の戦略が選択され、以下同様に続きます。
次に、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 は、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 つのスレッドのみがバインドされることが予測されます。
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)
は、現在のハードウェアでは 1 つのコアしかサポートできないため、スレッドが同じコアにのみバインドされることを意味します。 1 つのスレッドのバインドなので、最終結果は 1 つのスレッドにのみバインドされることが予測できます。実行結果は次のとおりです:
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,和之前的分析相匹配。
上面我们提到的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 中国語 Web サイトの他の関連記事を参照してください。