CPU 拓扑:从 SMP 谈到 NUMA (理论篇)

 Linux
 

文章首发于我的公众号「Linux云计算网络」,欢迎关注,第一时间掌握技术干货!

本文的一些概念依赖于上一篇 “CPU 拓扑:从 SMP 谈到 NUMA (理论篇)”,如果你对这块还没有概念,建议看完那篇再来看,如果对这块有自己独到的见解,欢迎探讨。

本文主要会侧重 NUMA 这块,我会通过自己的环境验证 NUMA 的几个概念,以便对 NUMA 的架构有个较深的印象。

NUMA 的几个概念:Node,Socket,Core,Thread


NUMA 技术的主要思想是将 CPU 进行分组,Node 即是分组的抽象,一个 Node 表示一个分组,一个分组可以由多个 CPU 组成。每个 Node 都有自己的本地资源,包括内存、IO 等。每个 Node 之间通过互联模块(QPI)进行通信,因此每个 Node 除了可以访问自己的本地内存之外,还可以访问远端 Node 的内存,只不过性能会差一些,一般用 distance 这个抽象的概念来表示各个 Node 之间互访资源的开销。

Node 是一个逻辑上的概念,与之相对的 Socket,是物理上的概念。它表示一颗物理 CPU 的封装,是主板上的 CPU 插槽,所以,一般就称之为插槽(敲黑板,这 Y 不是套接字吗?emmm……)

Core 就是 Socket 里独立的一组程序执行单元,称之为物理核。有了物理核,自然就有逻辑核,Thread 就是逻辑核。更专业的说法应该称之为超线程。

超线程是为了进一步提高 CPU 的处理能力,Intel 提出的新型技术。它能够将一个 Core 从逻辑上划分成多个逻辑核(一般是两个),每个逻辑核有独立的寄存器和中断逻辑,但是一个 Core 上的多个逻辑核共享 Core 内的执行单元和 Cache,频繁调度可能会引起资源竞争,影响性能。超线程必须要 CPU 支持才能开启。

综上所述,一个 NUMA Node 可以有一个或者多个 Socket,每个 Socket 也可以有一个(单核)或者多个(多核)Core,一个 Core 如果打开超线程,则会变成两个逻辑核(Logical Processor,简称 Processor)。

所以,几个概念从大到小排序依次是:

Node > Socket > Core > Processor

验证 CPU 拓扑


了解了以上基本概念,下面在实际环境中查看这些概念并验证。

Node

numactl --hardware 查看当前系统的 NUMA Node(numactl 是设定进程 NUMA 策略的命令行工具):

1
2
3
4
5
6
7
8
9
10
11
12
Linux # numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 12 13 14 15 16 17
node 0 size: 49043 MB
node 0 free: 20781 MB
node 1 cpus: 6 7 8 9 10 11 18 19 20 21 22 23
node 1 size: 49152 MB
node 1 free: 31014 MB
node distances:
node 0 1
0: 10 21
1: 21 10

可以得出的信息有:1)系统的 Node 数为 2;2)每个 Node 包含的 Processor 数为 12;3)每个 Node 的总内存大小和空闲内存大小;4)每个 Node 之间的 distance。

还可以查看 /sys/devices/system/node/ 目录,这里记录着具体哪些 Node。

Socket

/proc/cpuinfo 中记录着 Socket 信息,用 “physical id” 表示,可以用 cat /proc/cpuinfo | grep "physical id" 查看:

1
2
3
4
5
6
7
8
9
Linux # cat /proc/cpuinfo | grep "physical id"
physical id : 0
physical id : 0
physical id : 0
physical id : 0
physical id : 1
physical id : 1
physical id : 1
physical id : 1

可以看到有 2 个 Socket,我们还可以查看以下这几种变种:

1)查看有几个 Socket

1
2
3
4
Linux # grep 'physical id' /proc/cpuinfo | awk -F: '{print $2 | "sort -un"}'

0
1

1
2
3
Linux # grep 'physical id' /proc/cpuinfo | awk -F: '{print $2 | "sort -un"}' | wc -l

2

2)查看每个 Socket 有几个 Processor

1
2
3
4
Linux # grep 'physical id' /proc/cpuinfo | awk -F: '{print $2}' | sort | uniq -c

12 0
12 1

3)查看每个 Socket 有哪几个 Processor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Linux # awk -F: '{ 
> if ($1 ~ /processor/) {
> gsub(/ /,"",$2);
> p_id=$2;
> } else if ($1 ~ /physical id/){
> gsub(/ /,"",$2);
> s_id=$2;
> arr[s_id]=arr[s_id] " " p_id
> }
> }
>
> END{
> for (i in arr)
> print arr[i];
> }' /proc/cpuinfo | cut -c2-

0 1 2 3 4 5 12 13 14 15 16 17
6 7 8 9 10 11 18 19 20 21 22 23

Core

同样在 /proc/cpuinfo 中查看 Core 信息:

1
2
3
4
5
6
7
Linux # cat /proc/cpuinfo |grep "core id" | sort -u
core id : 0
core id : 1
core id : 2
core id : 3
core id : 4
core id : 5

上面的结果表明一个 Socket 有 5 个 Core。上面查到有 2 个 Socket,则一共就有 10 个 Core。

Processor

上面查看 Socket 信息时已经能够得到 Processor 的信息,总共有 24 个 Processor,不过也可以直接从 /proc/cpuinfo 中获取:

1)获取总的 Processor 数,查看 “processor” 字段:

1
2
3
Linux # cat /proc/cpuinfo | grep "processor" | wc -l

24

2)获取每个 Socket 的 Processor 数,查看 “siblings” 字段:

1
2
3
Linux # cat /proc/cpuinfo | grep "siblings" | sort -u

12

Cache

Cache 也一样通过 /proc/cpuinfo 查看:

1
2
3
4
5
processor  : 0

cache size : 15360 KB

cache_alignment : 64

不过这里的值 cache size 比较粗略,我们并不知道这个值是哪一级的 Cache 值(L1?L2?L3?),这种方法不能确定,我们换一种方法。

其实详细的 Cache 信息可以通过 sysfs 查看,如下:

比如查看 cpu0 的 cache 情况:

1
2
3
Linux # ls /sys/devices/system/cpu/cpu0/cache/

index0/ index1/ index2/ index3/

其中包含四个目录:
index0 存 L1 数据 Cache,index1 存 L1 指令 Cache,index2 存 L2 Cache,index3 存 L3 Cache。每个目录里面包含一堆描述 Cache 信息的文件。我们选 index0 具体看下:

其中,shared_cpu_list 和 shared_cpu_map 表示意思是一样的,都表示该 cache 被哪几个 processor 共享。对 shared_cpu_map 具体解释一下。

这个值表面上看是二进制,但其实是 16 进制,每个数字有 4 个bit,代表 4 个 cpu。比如上面的 001001 拆开后是:

1
0000 0000 0001 0000 0000 0001,1 bit 处即对应 cpu 标号,即 cpu0 和 cpu12。

同样我们可以对其他 index 进行统计,可以得出:
/proc/cpuinfo 中的 cache size 对应的 L3 Cache size

最后,综合以上所有信息我们可以绘制出一下的 CPU 拓扑图:

我们发现以上命令用得不太顺手,要获取多个数据需要输入多条命令,能不能一条命令就搞定,当然是有的,lscpu 就可以做到,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Linux # lscpu 
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 24 //共有24个逻辑CPU(threads)
On-line CPU(s) list: 0-23
Thread(s) per core: 2 //每个 Core 有 2 个 Threads
Core(s) per socket: 12 //每个 Socket 有 12 个 Threads
Socket(s): 2 //共有 2 个 Sockets
NUMA node(s): 2 //共有 2 个 Nodes
Vendor ID: GenuineIntel
CPU family: 6
Model: 63
Stepping: 2
CPU MHz: 2401.000
BogoMIPS: 4803.16
Virtualization: VT-x
L1d cache: 32K //L1 data cache 32k
L1 cache: 32K //L1 instruction cache 32k
L2 cache: 256K //L2 instruction cache 256k
L3 cache: 15360K //L3 instruction cache 15M
NUMA node0 CPU(s): 0-5,12-17
NUMA node1 CPU(s): 6-11,18-23

当然了,没有完美的命令,lscpu 也只能显示一些宽泛的信息,只是相对比较全面而已,更详细的信息,比如 Core 和 Cache 信息就得借助 cpuinfo 和 sysfs 了。

下面给大家提供一个脚本,能够比较直观的显示以上所有信息,有 shell 版的和 python 版的(不是我写的,文末附上了引用出处)。

大家有需要可以回复 “CPU” 获取,我就不贴出来了,显示的结果大概就是长下面这个样子:

python 版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
============================================================
Core and Socket Information (as reported by '/proc/cpuinfo')
============================================================

cores = [0, 1, 2, 3, 4, 5]

sockets = [0, 1]


Socket 0 Socket 1

-------- --------

Core 0 [0, 12] [6, 18]

Core 1 [1, 13] [7, 19]

Core 2 [2, 14] [8, 20]

Core 3 [3, 15] [9, 21]

Core 4 [4, 16] [10, 22]

Core 5 [5, 17] [11, 23]

Reference:

  1. 玩转 CPU 拓扑:
    http://blog.itpub.net/645199/viewspace-1421876/
  2. NUMA 体系结构详解
    https://blog.csdn.net/ustc_dylan/article/details/45667227(shell 代码引用)
  3. dpdk 源代码(python 代码引用)

PS:文章未经我允许,不得转载,否则后果自负。

–END–

欢迎扫👇的二维码关注我的微信公众号,后台回复「m」,可以获取往期所有技术博文推送,更多资料回复下列关键字获取。