Uso avanzado de Java: afinidad de subprocesos de subprocesos vinculados a la CPU

¡Acostúmbrate a escribir juntos! Este es el día 13 de mi participación en el "Nuevo plan diario de Nuggets · Desafío de actualización de abril", haga clic para ver los detalles del evento .

Introducción

En los sistemas informáticos modernos, puede haber varias CPU y cada CPU puede tener varios núcleos. Para aprovechar al máximo las capacidades de las CPU modernas, JAVA introduce subprocesos múltiples, y diferentes subprocesos pueden ejecutarse en diferentes CPU o en diferentes núcleos de CPU al mismo tiempo. Pero para los programadores de JAVA, cuántos subprocesos se crean pueden ser controlados por ellos mismos, pero en qué CPU se ejecuta el subproceso es una caja negra, que generalmente es difícil de saber.

Sin embargo, si diferentes núcleos de CPU programan el mismo subproceso, es posible que se produzca una pérdida de rendimiento provocada por el cambio de CPU. En circunstancias normales, esta pérdida es relativamente pequeña, pero si su programa está particularmente preocupado por la pérdida causada por este cambio de CPU, puede probar la afinidad de subprocesos de Java de la que hablaré hoy.

Introducción a la afinidad de subprocesos de Java

La afinidad de subprocesos de Java se utiliza para vincular subprocesos en código JAVA a núcleos de CPU específicos para mejorar el rendimiento del programa.

Obviamente, para interactuar con la CPU subyacente, Java Thread Affinity debe usar el método de interacción entre JAVA y los métodos nativos. Aunque JNI es el método oficial de JAVA para interactuar con los métodos nativos, JNI es más engorroso de usar. . Entonces, java thread Affinity en realidad usa JNA, que es una biblioteca mejorada basada en JNI que interactúa con métodos nativos.

Primero, introduzcamos varios conceptos en la CPU, a saber, CPU, zócalo de CPU y núcleo de CPU.

El primero es la CPU El nombre completo de la CPU es la unidad central de procesamiento, también conocida como la unidad central de procesamiento, que es el núcleo clave utilizado para el procesamiento de tareas.

Entonces, ¿qué es un zócalo de CPU? El llamado zócalo es el zócalo donde se inserta la CPU.Si ha ensamblado una computadora de escritorio, debe saber que la CPU está instalada en el zócalo.

CPU Core se refiere a la cantidad de núcleos en la CPU. Hace mucho tiempo, las CPU eran de un solo núcleo, pero con el desarrollo de la tecnología de múltiples núcleos, una CPU puede contener múltiples núcleos, y los núcleos de la CPU son el negocio real. unidad de procesamiento

Si está en una máquina Linux, puede ver el estado de la CPU del sistema usando el comando lscpu, de la siguiente manera:

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

Del resultado anterior podemos ver que este servidor tiene un socket, cada socket tiene un núcleo y cada núcleo puede procesar 1 subproceso al mismo tiempo.

La información de estas CPU se puede llamar diseño de CPU. En Linux, la información de diseño de la CPU se almacena en /proc/cpuinfo.

Hay una interfaz CpuLayout en Java Thread Affinity para corresponder a esta información:

public interface CpuLayout {
    
    int cpus();

    int sockets();

    int coresPerSocket();

    int threadsPerCore();

    int socketId(int cpuId);

    int coreId(int cpuId);

    int threadId(int cpuId);
}
复制代码

De acuerdo con la información del diseño de la CPU, AffinityStrategies proporciona algunas estrategias básicas de Affinity para organizar la relación de distribución entre diferentes subprocesos, que incluyen principalmente lo siguiente:

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

复制代码

Estas estrategias también se distinguen según el socketId y coreId de CpuLayout, tomamos SAME_CORE como ejemplo y presionamos su implementación específica:

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 estrategia Affinity se puede ordenar, la estrategia de enfrente se emparejará primero, si no coincide, se seleccionará la segunda estrategia, y así sucesivamente.

Uso de AffinityLock

A continuación, veamos el uso específico de Affinity, el primero es obtener un bloqueo de CPU, antes de JAVA7, podemos escribir:

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

Después de JAVA7, se puede escribir así:

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

El método de adquisición de bloqueo puede adquirir cualquier CPU disponible para el subproceso. Este es un bloqueo de grano grueso. Si desea obtener un núcleo de grano fino, puede usar adquirirCore:

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

adquirirLock también tiene un parámetro de vinculación, que indica si vincular el subproceso actual al bloqueo de CPU adquirido.Si el parámetro de vinculación = verdadero, el subproceso actual se ejecutará en la CPU obtenida en adquirirLock. Si el parámetro de vinculación = falso, significa que adquirirLock se vinculará en algún momento en el futuro.

Mencionamos AffinityStrategy anteriormente, esta AffinityStrategy se puede usar como un parámetro de adquirirLock:

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

Tenga en cuenta que debido a que la capa inferior de BitSet usa Long para el almacenamiento de datos, el índice aquí es el índice de bits, por lo que debemos convertir el índice de CPU decimal.

Resumir

Java Thread Affinity puede controlar la CPU utilizada por Thread en el programa desde el código JAVA, que es muy potente y puede ser utilizado por todos.

Este artículo ha sido incluido en www.flydean.com/01-java-thr…

¡La interpretación más popular, los productos secos más profundos, los tutoriales más concisos y muchos trucos que no conoces están esperando que los descubras!

Bienvenido a prestar atención a mi cuenta oficial: "Programe esas cosas", entienda la tecnología, ¡entiéndalo mejor!

Supongo que te gusta

Origin juejin.im/post/7085981545277685790
Recomendado
Clasificación