关于java:java高级用法之绑定CPU的线程ThreadAffinity

8次阅读

共计 5475 个字符,预计需要花费 14 分钟才能阅读完成。

简介

在古代计算机系统中,能够有多个 CPU,每个 CPU 又能够有多核。为了充分利用古代 CPU 的性能,JAVA 中引入了多线程,不同的线程能够同时在不同 CPU 或者不同 CPU 核中运行。然而对于 JAVA 程序猿来说创立多少线程是能够本人管制的,然而线程到底运行在哪个 CPU 上,则是一个黑盒子,一般来说很难得悉。

然而如果是不同 CPU 核查同一线程进行调度,则可能会呈现 CPU 切换造成的性能损失。个别状况下这种损失是比拟小的,然而如果你的程序特地在意这种 CPU 切换带来的损耗,那么能够试试明天要讲的 Java Thread Affinity.

Java Thread Affinity 简介

java thread Affinity 是用来将 JAVA 代码中的线程绑定到 CPU 特定的核上,用来晋升程序运行的性能。

很显然,要想和底层的 CPU 进行交互,java thread Affinity 肯定会用到 JAVA 和 native 办法进行交互的办法,JNI 尽管是 JAVA 官网的 JAVA 和 native 办法进行交互的办法,然而 JNI 在应用起来比拟繁琐。所以 java thread Affinity 理论应用的是 JNA,JNA 是在 JNI 的根底上进行改进的一种和 native 办法进行交互的库。

先来介绍 CPU 中几个概念,别离是 CPU,CPU socket 和 CPU core。

首先是 CPU,CPU 的全称就是 central processing unit, 又叫做中央处理器, 就是用来进行工作解决的要害外围。

那么什么是 CPU socket 呢?所谓 socket 就是插 CPU 的插槽,如果组装过台式机的同学应该都晓得,CPU 就是装置在 Socket 上的。

CPU Core 指的是 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

从下面的输入咱们能够看到,这个服务器有一个 socket,每个 socket 有一个 core,每个 core 能够同时解决 1 个线程。

这些 CPU 的信息能够称为 CPU layout。在 linux 中 CPU 的 layout 信息是寄存在 /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 layout 的信息,AffinityStrategies 提供了一些根本的 Affinity 策略,用来安顿不同的 thread 之间的散布关系,次要有上面几种:

    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);
        }
    }

Affinity 策略能够有程序,在后面的策略会首先匹配,如果匹配不上则会抉择第二策略,依此类推。

AffinityLock 的应用

接下来咱们看下 Affinity 的具体应用, 首先是取得一个 CPU 的 lock, 在 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。这个是一个粗粒度的 lock。如果想要取得细粒度的 core,能够用 acquireCore:

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

acquireLock 还有一个 bind 参数,示意是否将以后的线程绑定到取得的 cpu lock 上,如果 bind 参数 =true,那么以后的 thread 会在 acquireLock 中取得的 CPU 上运行。如果 bind 参数 =false,示意 acquireLock 会在将来的某个时候进行 bind。

下面咱们提到了 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 来进行数据存储的,所以这里的 index 是 bit index,所以咱们须要对十进制的 CPU index 进行转换。

总结

Java Thread Affinity 能够从 JAVA 代码中对程序中 Thread 应用的 CPU 进行管制,十分弱小,大家能够使用起来。

本文已收录于 http://www.flydean.com/01-java-thread-affinity/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

正文完
 0