numa topology

NUMA

Non-Uniform Memory Access (NUMA) is a computer memory designused in multiprocessing, where the memory access time depends on the memorylocation relative to a processor. Under NUMA, a processor can access its ownlocal memory faster than non-local memory, that is, memory local to anotherprocessor or memory shared between processors.NUMA architectures logicallyfollow in scaling from symmetric multiprocessing (SMP) architectures.

A fairly technically correct and alsofairly ugly definition of a node is: a region of memory in which every byte hasthe same distance from each CPU.
A more common definition is: a block of memory and the CPUs, I/O, etc.physically on the same bus as the memory.

NUMA系统拥有多条内存总线,于是将几个处理器通过内存总线与一块内存相连构成一个组,这样整个庞大的系统就可以被分为若干个组,这个组的概念在NUMA系统中被称为节点(node)。处于该节点中的内存被称为本地内存(local memory),处于其他节点中的内存对于该组而言被称为外部内存(foreign memory)。而节点又可以分为三类,即本地节点(local node),邻居节点(neighbour node)和远端节点(remote node).

本地节点:对于某个节点中的所有CPU,此节点称为本地节点;

邻居节点:与本地节点相邻的节点称为邻居节点;

远端节点:非本地节点或邻居节点的节点,称为远端节点。

超立方体可以作为一种有效的拓扑来描述NUMA系统,它将系统中的节点数限制在2^C内,C是每个节点拥有的邻居节点数,如下图所示

 

以C=3为例,则对于节点1而言,2,3,5则为邻居节点,4,6,7,8为远端节点,显然访问开销的关系为 本地节点<邻居节点<远端节点。

下图展示了这些术语之间的逻辑关系:
 
一个NUMAnode包括一个或者多个Socket,以及与之相连的localmemory。一个多核的Socket有多个Core。如果CPU支持HT,OS还会把这个Core看成 2个LogicalProcessor。为了避免混淆,在下文中统一用socket指代Processoror Socket;为了偷懒,下文中用Processor指代LogicalProcessor。

查看CPU Topology

本文以Red Hat Enterprise LinuxServer release 5.4为例介绍。

NUMA Node

第一种方法使用numactl查看
numactl –hardware

1

2

3

4

5

6

7

8

9

available: 2 nodes (0-1)  //当前机器有2个NUMA node,编号0&1

node 0 size: 12091 MB  //node 0 物理内存大小

node 0 free: 988 MB    //node 0 当前free内存大小

node 1 size: 12120 MB

node 1 free: 1206 MB

node distances:        //node 距离,可以简单认为是CPU本node内存访问和跨node内存访问的成本。从下表可知跨node的内存访问成本(20)是本地node内存(10)的2倍。

node   0   1

  0:  10  20

  1:  20  10

第二种方法是通过sysfs查看,这种方式可以查看到更多的信息
ls/sys/devices/system/node/

1

node0  node1 //两个目标表示本机有2个node,每个目录内部有多个文件和子目录描述node内cpu,内存等信息。比如说node0/meminfo描述了node0内存相关信息。

Socket

可以直接通过/proc/cpuinfo查看,cpuinfo里的physical id描述的就是Socket的编号,
cat /proc/cpuinfo|grep“physical id”

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

]

physical id     : 0

physical id     : 0

physical id     : 0

physical id     : 0

physical id     : 1

physical id     : 1

physical id     : 1

physical id     : 1

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,编号为0和1。
还可以简单的使用如下命令直接查看Socket个数
cat /proc/cpuinfo|grep“physical id”|sort -u|wc –l

1

2   //本机有2个物理CPU封装

Core

仍然是可以通过/proc/cpuinfo查看,cpuinfo中跟core相关的信息有2行。
cpu cores : 4 //一个socket有4个核,
core id : 1 //一个core在socket内的编号
通过如下命令可以直接查看core的数量
cat /proc/cpuinfo |grep“cpu cores”|uniq|cut -d: -f2

1

4  //1个socket有4个core

本机有2个socket,每个有4个core,所以一共有8个core
还可以查看core在Socket里的编号
cat /proc/cpuinfo|grep“core id”|sort -u

1

2

3

4

core id         : 0

core id         : 1

core id         : 10

core id         : 9

一个socket里面4个core的编号为0,1,9,10。是的,core id是不连续的。如果谁知道为啥麻烦通知我,先谢了。

Logical Processor

仍然是可以通过/proc/cpuinfo查看在OS的眼里有多少个Logical Processor
cat /proc/cpuinfo |grepprocessor|wc –l

1

 

Ok,8个core变成了16个Logical Processor,所以本机开启了HT。
问题来了,cpuinfo里面16个Processor编号为0-15,Core的id为0,1,9,10,Socket的id为0,1。这些编号是如何对应的呢?
我们查看一个Processor完整的cpuinfo就比较清楚了,我剔除了不相关的行:

processor : 0

processor : 5

physical id : 0
siblings : 8
core id : 0
cpu cores : 4

physical id : 1
siblings : 8
core id : 1
cpu cores : 4

明白了?
Processor 0:在socket 0的core 0 里。
Processor 5:在socket 1的core 1 里。

Cache

仍然可以通过/proc/cpuinfo查看,OMG, cpuinfo难道是万能的?

1

2

3

processor       : 0

cache size      : 12288 KB //cpu cache 大小

cache_alignment : 64 

问题又来了,我们知道CPUcache分为L1,L2,L3, L1一般还分为独立的指令cache和数据cache。Cpuinfo里这个cache size指的是?

好吧,cpuinfo也不是万能的。详细的cache信息可以通过sysfs查看
ls/sys/devices/system/cpu/cpu0/cache/

1

index0  index1  index2  index3

4个目录
index0:1级数据cache
index1:1级指令cache
index2:2级cache
index3:3级cache ,对应cpuinfo里的cache
目录里的文件是cache信息描述,以本机的cpu0/index0为例简单解释一下:

文件

内容

说明

type

Data

数据cache,如果查看index1就是Instruction

Level

1

L1

Size

32K

大小为32K

coherency_line_size

64

64*4*128=32K

physical_line_partition

1

ways_of_associativity

4

number_of_sets

128

shared_cpu_map

00000101

表示这个cache被CPU0和CPU8 share

解释一下shared_cpu_map内容的格式:
表面上看是2进制,其实是16进制表示,每个bit表示一个cpu,1个数字可以表示4个cpu
截取00000101的后4位,转换为2进制表示

CPU id

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0×0101的2进制表示

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

1

0101表示cpu8和cpu0,即cpu0的L1 data cache是和cpu8共享的。
验证一下?
cat/sys/devices/system/cpu/cpu8/cache/index0/shared_cpu_map
00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000101
再看一下index3 shared_cpu_map的例子
cat/sys/devices/system/cpu/cpu0/cache/index3/shared_cpu_map
00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000f0f

CPU id

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x0f0f的2进制表示

0

0

0

0

1

1

1

1

0

0

0

0

1

1

1

1

cpu0,1,2,3和cpu8,9,10,11共享L3 cache

小结

综合以上信息可以绘制出以下的cpu topology图:

 

使用CPU Topology

好吧,现在我们知道了如何查看CPUtopology。那么这与各位攻城狮的工作有什么关系呢?

以淘宝搜索常见的服务模型为例,服务端把离线处理的数据load到内存中,开始监听某个服务端口,接收到客户端请求后从线程池中分配一个工作线程,该线程解析请求,读取内存中对应的数据,进行一些计算,然后把结果返回给客户端。
把这个过程简化简化再简化,抽象抽象再抽象,可以得到一个简单的测试程序,程序流程为:
1. 主线程申请2块256M的内存,使用memset初始化这两块内存的每个byte
2. 启动2个子线程,每个线程内循环16M次,在每次循环中随机读取2块内存中的各1K数据,对每个byte进行简单加和,返回。
3. 主线程等待子线程结束,打印每个线程的结果,结束。
使用-O2编译出可执行文件test,分别使用下面2个命令运行该程序。运行时间和机器配置以及当前load有关,绝对值没有意义,这里仅比较相对值。

命令

time ./test

time numactl -m 0 –physcpubind=2,3 ./test

用时

real 0m38.678s
user 1m6.270s
sys 0m5.569s

real 0m28.410s
user 0m54.997s
sys 0m0.961s

发生了什么?为什么有这么大的差异?
第一个命令直观,那么我们看一下第二个命令具体做了什么:
numactl -m 0–physcpubind=2,3 ./test
-m 0:在node 0上分配内存
–physcpubind=2,3:在cpu 2和3上运行程序,即一个线程运行在cpu2上,另一个运行在cpu3上。

参考上面的CPUtopology图就很容易理解了,由于线程绑定cpu2和3执行,共享了L3 cache,且全部内存都是本node访问,运行效率自然比随机选择cpu运行,运行中还有可能切换cpu,内存访问有可能跨node的第一种方式要快了。

接下来,让我们看看完整的表格,读者可以看看有没有惊喜:

情况

命令

用时

解释

完全由OS控制

time ./test

real 0m38.678s
user 1m6.270s
sys 0m5.569s

乐观主义者,甩手掌柜型

绑定跨node的Cpu执行

time numactl –physcpubind=2,6 ./test

real 0m38.657s
user 1m7.126s
sys 0m5.045s

Cpu 2和6不在同一个node,不能share L3 cache

绑定单node的Cpu执行

time numactl –physcpubind=2,3 ./test

real 0m28.605s
user 0m55.161s
sys 0m0.856s

Cpu 2和3在同一个node,share L3 cache。内存使用由OS控制,一般来说node 0和1内存都会使用。

跨node内存访问+绑定单node CPU执行

time numactl -m 1 –physcpubind=2,3 ./test

real 0m33.218s
user 1m4.494s
sys 0m0.911s

内存全使用node1,2个cpu在node0,内存访问比较吃亏

单node内存访问+绑定本node CPU执行

time numactl -m 0 –physcpubind=2,3 ./test

real 0m28.367s
user 0m55.062s
sys 0m0.825s

内存&cpu都使用node0

单node内存访问+绑定本node 单core执行

time numactl -m 0 –physcpubind=2,10 ./test

real 0m58.062s
user 1m55.520s
sys 0m0.270s

CPU2和10不但在同一个node,且在同一个core,本意是希望共享L1,L2cache,提升性能。但是不要忘了,CPU2和10是HT出来的logical Processor,在本例cpu密集型的线程中硬件争用严重,效率急剧下降。有没有发现和上一个case的时间比率很有意思?

现在谁还能说了解点cpu topology没用呢?☺

Tips

补充几个小tips,方便有兴趣的同学分析上面表格的各个case

1.查看进程的内存numa node分布

简单的说可以查看进程的numa_maps文件
cat /proc//numa_maps
文件格式可以直接:man numa_maps
为了避免输入数字pid,我使用如下命令查看:
cat /proc/$(pidoftest|cut –d” ” -f1)/numa_maps

2.查看线程run在哪个processor

可以使用top命令查看一个进程的各个线程分别run在哪个processor上
同样,为了避免输入数字pid,我使用如下命令启动top:
top -p$(pidof test |sed-e ‘s/ /,/g’)
在默认配置下不显示线程信息,需要进入Top后按“shift+H”,打开线程显示。
另外,如果没有P列,还需要按“f”,按“j”,添加,这一列显示的数字就是这个线程上次run的processor id。
关于top的使用,请读者自行man top

3.另一种绑定cpu执行的方法

如果读者的程序不涉及大量内存的访问,可以通过taskset绑定cpu执行。别怪我没提醒你,仔细判断是否应该绑定到同一个core的processor上哦。
关于命令的使用,请读者自行Mantaskset    

这些内容整合自:

http://blog.csdn.net/vanbreaker/article/details/7492886

http://www.searchtb.com/2012/12/玩转cpu-topology.html

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值