Dans les systèmes informatiques modernes, il peut y avoir plusieurs processeurs, et chaque processeur peut avoir plusieurs cœurs. Afin d'utiliser pleinement les fonctions des processeurs modernes, le multithreading a été introduit dans JAVA. Différents threads peuvent s'exécuter simultanément sur différents processeurs ou différents cœurs de processeur. Mais pour les programmeurs JAVA, ils peuvent contrôler le nombre de threads qu'ils créent, mais sur quel processeur les threads s'exécutent est une boîte noire, et il est généralement difficile de le savoir.
Mais si différents cœurs de processeur planifient le même thread, il peut y avoir une perte de performances causée par le changement de processeur. Dans des circonstances normales, cette perte est relativement faible, mais si votre programme est particulièrement préoccupé par la perte causée par ce type de changement de processeur, vous pouvez alors essayer Java Thread Affinity dont je vais parler aujourd'hui.
Affinité des threads Java Il est utilisé pour lier les threads du code JAVA à des cœurs spécifiques du processeur afin d'améliorer les performances d'exécution du programme.
Évidemment, afin d'interagir avec le processeur sous-jacent, le thread Java Affinity utilisera certainement la méthode d'interaction avec JAVA et les méthodes natives. Bien que JNI soit la méthode officielle d'interaction avec JAVA et les méthodes natives, JNI n'est pas facile à utiliser. Plutôt encombrant. Par conséquent, le thread Java Affinity utilise en fait JNA. JNA est une bibliothèque améliorée sur la base de JNI et interagit avec les méthodes natives.
Tout d’abord, introduisons plusieurs concepts du CPU, à savoir le CPU, le socket CPU et le cœur du CPU.
Le premier est le CPU. Le nom complet du CPU est unité centrale de traitement, également appelée unité centrale de traitement, qui est le cœur clé utilisé pour le traitement des tâches.
Alors, qu'est-ce qu'un socket CPU ? Le soi-disant socket est l'emplacement où le CPU est inséré. Si vous avez assemblé un ordinateur de bureau, sachez que le CPU est installé sur le socket.
CPU Core fait référence au nombre de cœurs du processeur. Il y a longtemps, les processeurs étaient tous monocœurs. Cependant, avec le développement de la technologie multicœur, un processeur peut contenir plusieurs cœurs, et les cœurs du processeur. sont ce qui fait réellement fonctionner l'unité de traitement.
Si vous êtes sur une machine Linux, vous pouvez vérifier l'état du processeur du système en utilisant la commande lscpu, comme indiqué ci-dessous :
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
À partir de la sortie ci-dessus, nous pouvons voir que ce serveur a un socket, et chaque socket en a un, chaque cœur peut traiter 1 thread en même temps.
Ces informations sur le processeur peuvent être appelées disposition du processeur. Sous Linux, les informations de configuration du processeur sont stockées dans /proc/cpuinfo.
Il existe une interface CpuLayout dans Java Thread Affinity pour correspondre à ces informations :
public interface CpuLayout { int cpus(); int sockets(); int coresPerSocket(); int threadsPerCore(); int socketId(int cpuId); int coreId(int cpuId); int threadId(int cpuId); }
Selon les informations de disposition du processeur, AffinityStrategies fournit des stratégies d'affinité de base pour organiser la relation de distribution entre les différents threads, principalement. Il existe les types suivants :
SAME_CORE - 运行在同一个core中。 SAME_SOCKET - 运行在同一个socket中,但是不在同一个core上。 DIFFERENT_SOCKET - 运行在不同的socket中 DIFFERENT_CORE - 运行在不同的core上 ANY - 任何情况都可以
Ces stratégies se distinguent également en fonction du socketId et du coreId de CpuLayout. Prenons SAME_CORE comme exemple et appuyons sur son implémentation spécifique :
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); } }
Les stratégies d'affinité peuvent être dans l'ordre, dans la stratégie précédente, elles correspondront en premier, s'il n'y en a pas. match, la deuxième stratégie sera sélectionnée, et ainsi de suite.
Regardons ensuite l'utilisation spécifique d'Affinity La première est d'obtenir un verrouillage CPU Avant JAVA7, on peut écrire comme ceci :
AffinityLock al = AffinityLock.acquireLock(); try { // do some work locked to a CPU. } finally { al.release(); }
Après JAVA7, on peut écrire comme ceci :
try (AffinityLock al = AffinityLock.acquireLock()) { // do some work while locked to a CPU. }
. Méthode acquireLock Tout processeur disponible peut être obtenu pour le thread. Il s'agit d'une serrure à gros grains. Si vous souhaitez obtenir un noyau à granularité fine, vous pouvez utiliser acquireCore :
try (AffinityLock al = AffinityLock.acquireCore()) { // do some work while locked to a CPU. }
acquireLock a également un paramètre de liaison, indiquant s'il faut lier le thread actuel au verrou du processeur acquis. Si le paramètre de liaison = true, alors le thread actuel le fera. être en Exécution sur le CPU obtenu dans acquireLock. Si le paramètre de liaison = false, cela signifie que acquireLock sera lié à un moment donné dans le futur.
Nous avons mentionné AffinityStrategy ci-dessus. Cette AffinityStrategy peut être utilisée comme paramètre d'acquisitionLock :
public AffinityLock acquireLock(AffinityStrategy... strategies) { return acquireLock(false, cpuId, strategies); }
En appelant la méthode acquireLock de l'AffinityLock actuel, le thread actuel peut se voir attribuer un AffinityLock lié à la stratégie de verrouillage précédente.
AffinityLock fournit également une méthode dumpLocks pour afficher l'état actuel de la liaison du processeur et du thread. Prenons un exemple :
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);
Dans le code ci-dessus, nous avons créé un pool de threads de 4 threads, la ThreadFactory correspondante est AffinityThreadFactory, nommée le pool de threads bg, et alloué 3 AffinityStrategy. Cela signifie d'abord allouer au même cœur, puis à un socket différent et enfin à n'importe quel processeur disponible.
Ensuite, au cours du processus d'exécution spécifique, nous avons soumis 12 threads, mais notre pool de threads n'a qu'un maximum de 4 threads. On peut prédire que seuls 4 threads dans les résultats renvoyés par la méthode AffinityLock.dumpLocks seront liés au CPU. . Jetons un coup d'oeil :
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
Comme vous pouvez le voir sur la sortie, CPU0 n'est pas disponible. Les 7 autres processeurs sont disponibles, mais n'ont que 4 threads qui leur sont liés, ce qui correspond à notre analyse précédente.
Ensuite, nous modifions l'AffinityStrategy d'AffinityThreadFactory comme suit :
new AffinityThreadFactory("bg", SAME_CORE)
signifie que les threads ne seront liés qu'au même noyau, car dans le matériel actuel, un seul noyau ne peut prendre en charge la liaison que d'un seul thread en même temps. on peut donc prédire que le résultat final ne sera lié qu'à un seul thread, et les résultats en cours sont les suivants :
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进行转换。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!