Utilisation avancée de Java : Thread-Affinity des threads liés au processeur

Prenez l'habitude d'écrire ensemble ! C'est le 13ème jour de ma participation au "Nuggets Daily New Plan · April Update Challenge", cliquez pour voir les détails de l'événement .

Introduction

Dans les systèmes informatiques modernes, il peut y avoir plusieurs processeurs, et chaque processeur peut avoir plusieurs cœurs. Afin de tirer pleinement parti des capacités des processeurs modernes, JAVA introduit le multithreading, et différents threads peuvent s'exécuter sur différents processeurs ou différents cœurs de processeur en même temps. Mais pour les programmeurs JAVA, le nombre de threads créés peut être contrôlé par eux-mêmes, mais sur quel processeur le thread s'exécute est une boîte noire, ce qui est généralement difficile à savoir.

Cependant, si différents cœurs de processeur planifient le même thread, une perte de performances causée par le changement de processeur peut se produire. 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 changement de CPU, vous pouvez essayer le Java Thread Affinity dont je vais parler aujourd'hui.

Introduction à l'affinité de thread Java

L'affinité de thread Java est utilisée pour lier les threads du code JAVA à des cœurs de processeur spécifiques afin d'améliorer les performances du programme.

Évidemment, pour interagir avec le CPU sous-jacent, le thread java Affinity doit utiliser la méthode d'interaction entre JAVA et les méthodes natives. Bien que JNI soit la méthode officielle de JAVA pour interagir avec les méthodes natives, JNI est plus lourd à utiliser. . Ainsi, Java Thread Affinity utilise en fait JNA, qui est une bibliothèque améliorée basée sur JNI qui interagit avec les méthodes natives.

Introduisons d'abord plusieurs concepts dans le processeur, à savoir le processeur, le socket du processeur et le cœur du processeur.

Le premier est le CPU.Le nom complet du CPU est l'unité centrale de traitement, également appelée unité centrale de traitement, qui est le noyau clé utilisé pour le traitement des tâches.

Qu'est-ce qu'un socket CPU ? Le soi-disant socket est le socket où le CPU est inséré.Si vous avez assemblé un ordinateur de bureau, vous devez savoir que le CPU est installé sur le socket.

CPU Core fait référence au nombre de cœurs dans le CPU. Il y a longtemps, les CPU étaient à un seul cœur, mais avec le développement de la technologie multicœur, un CPU peut contenir plusieurs cœurs, et les cœurs du CPU sont la vraie affaire. unité de traitement.

Si vous êtes sur une machine Linux, vous pouvez afficher l'état du processeur du système à l'aide de la commande lscpu, comme suit :

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, chaque socket a un cœur et chaque cœur peut traiter 1 thread en même temps.

Les informations de ces processeurs peuvent être appelées disposition du processeur. Sous Linux, les informations de disposition du CPU 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 la disposition du processeur, AffinityStrategies fournit quelques stratégies d'affinité de base pour organiser la relation de distribution entre les différents threads, notamment les éléments suivants :

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

复制代码

Ces stratégies se distinguent également selon le socketId et le coreId de CpuLayout. Nous 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);
        }
    }
复制代码

La stratégie d'affinité peut être commandée, la stratégie en tête sera mise en correspondance en premier, si elle ne correspond pas, la deuxième stratégie sera sélectionnée, et ainsi de suite.

Utilisation d'AffinityLock

Intéressons-nous ensuite à l'utilisation spécifique d'Affinity, la première est d'obtenir un verrou CPU, avant JAVA7, on peut écrire :

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

Après JAVA7, il peut être écrit comme ceci :

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

La méthodeacquiseLock peut acquérir n'importe quel processeur disponible pour le thread. Il s'agit d'une serrure à gros grains. Si vous souhaitez obtenir un noyau à grain fin, vous pouvez utiliser AcquireCore :

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

AcquérirLock a également un paramètre de liaison, indiquant s'il faut lier le thread actuel au verrou de processeur acquis. Si le paramètre de liaison = vrai, le thread actuel s'exécutera sur le CPU obtenu dans l'acquisition de verrou. Si le paramètre bind = false, cela signifie qu'acquiertLock sera lié à un moment donné dans le futur.

Nous avons mentionné AffinityStrategy ci-dessus, cette AffinityStrategy peut être utilisée comme paramètre d'acquiertLock :

    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.
复制代码

Notez que, comme la couche inférieure de BitSet utilise Long pour le stockage des données, l'index ici est l'index de bits, nous devons donc convertir l'index décimal du processeur.

Résumer

Java Thread Affinity peut contrôler le CPU utilisé par Thread dans le programme à partir du code JAVA, qui est très puissant et peut être utilisé par tout le monde.

Cet article a été inclus dans www.flydean.com/01-java-thr…

L'interprétation la plus populaire, les marchandises sèches les plus profondes, les tutoriels les plus concis et de nombreuses astuces que vous ne connaissez pas vous attendent !

Bienvenue à prêter attention à mon compte officiel : "Programmer ces choses", comprendre la technologie, mieux vous comprendre !

Je suppose que tu aimes

Origine juejin.im/post/7085981545277685790
conseillé
Classement