此文严格意义上说,我没有很大的把握,其中关于CPU调度域的概念现在还有些混淆,但还是发出来,希望能做点铺路的贡献吧。
另外CPU调度域的原理我没办法自己写,能力尚浅, 只能将IBM知识库 和 另一位博主的文章摘录如下,并添加了些帮助理解的图片,希望能帮助到初学者。
进程绑定CPU的两种方法对比:Scheduling Domains 和 taskset
实际上进行CPU绑定的方法共有三种: numactl 、 taskset 和 Scheduling Domains。
在本文的最后,我将这numa 和 Scheduling Domain的原理整理了下,numa 其实和Scheduling Domains要结合起来看,详见文末。
scheduling domains方式:
从找到的一些资料中,给我的印象是它适合程序开发时,直接将程序绑定到某个核心上(即仅设置对某CPU的亲和性),
而程序启动后,在同这种方式来将进程绑定到某CPU上时,效果似乎并不太好。从下面mysql和httpd通过taskset 和
scheduling domains的对比可以有较好的示例说明。
先将我认识到的不太理想的地方列举如下:
1. 绑定必须依赖与PID(进程号)、LWP(轻量级进程号:即线程号)
2. 将PID、LWP号绑定到tasks中后,并不一定它们就真的会马上迁移到指定CPU上。
taskset方式:
这种方式是最常用的一种绑定CPU的方式,简单明了。
==================================【实测部分】===========================
下面是我的测试环境说明:
1. 测试环境为:VMware中的启动CentOS6.7, 在其中配置KVM,并启动最小化安装的RHEL6.4的KVM虚拟机.
2. CentOS6.7 的内存 :2G , CPU : 4
KVM虚拟机的内存:512M,CPU : 2
qemu-kvm -name rhel64-01 -m 512 -smp 2 -drive file=/images/kvm/rhel64-002.img,if=virtio,media=disk,cache=writeback,format=qcow2 -net nic,macaddr=54:52:00:00:00:03 -net tap,script=/etc/kvm/if-up,downscript=no -vnc :1 &
3. 安装mysql: 5.1.73
httpd: 2.2.15
4. 在CentOS6.7上通过sysbench 和 ab来模拟压力,看mysql 和 httpd在CPU0 和 CPU1之间切换的是否频繁.
cat mytest.sh #!/bin/bash case $1 in prepare) sysbench --test=oltp --db-driver=mysql --mysql-host=192.168.137.10 --mysql-user=db1 --mysql-password=db1 --mysql-db=db1 --oltp-table-name=t1 --mysql-table-engine=MyISAM --oltp-table-size=1000000 prepare ;; run) sysbench --test=oltp --db-driver=mysql --mysql-host=192.168.137.10 --mysql-port=3306 --mysql-user=db1 --mysql-password=db1 --mysql-db=db1 --oltp-table-name=t1 --mysql-table-engine=MyISAM --mysql-engine-trx=no --oltp-skip-trx=on --oltp-table-size=1000000 --max-requests=1000 --num-threads=100 run ;; cleanup) sysbench --test=oltp --db-driver=mysql --mysql-host=192.168.137.10 --mysql-user=db1 --mysql-password=db1 --mysql-db=db1 --oltp-table-name=t1 cleanup ;; esac
cat httpdtest.sh #!/bin/bash ab -n 30000 -c 100
5. 收集mysql 和 httpd每0.5秒其线程在CPU0 和 CPU1上的变化情况的监控脚本:
[root@node2 ~]# cat m.sh #!/bin/bash main(){ monps="ps -eLo ruser,lwp,psr" rm -f httpd.0 mysql.0 httpd.1 mysql.1 total echo -e "\t\e[32mCPU 0\e[0m\t\t\e[33mCPU 1\e[0m" echo -e "\t\e[32mhttpd | mysqld\e[0m\t\e[33mhttpd | mysqld\e[0m" $monps |grep -E 'apache|mysql' |while read line; do echo $line |awk '$3==0{if($1=="apache"){printf "\t%d\n",$2 >> "httpd.0"}else{printf "%d\n",$2 >> "mysql.0"}}' echo $line |awk '$3==1{if($1=="apache"){printf "%d\n",$2 >> "httpd.1"}else{printf "%d\n",$2 >> "mysql.1"}}' echo $line >> total done maxline=`cat total |grep -E 'apache|mysql' |awk '{if($1=="apache"){a[$3]++}else{m[$3]++}}END{max=(a[0]>a[1])?((a[0]>m[0])?a[0]:m[0]):((a[1]>m[0])?a[1]:m[0]);print (max>m[1])?max:m[1]}'` for f in httpd.0 mysql.0 httpd.1 mysql.1;do lnum=`cat $f |wc -l` if [ $lnum -lt $maxline ];then l=$(($maxline-$lnum)) for i in `seq 1 1 $l`;do [ "$f" == "httpd.0" ] && echo -e "\t-" >> $f || echo '-' >> $f done fi done paste httpd.0 mysql.0 httpd.1 mysql.1 |cat -n } rm -f b while :;do sleep 0.5 main >> b done
4. 没有配置Scheduling Domains 和 taskset前:
(1) 初始状态,httpd 和 mysqld在cpu0 和 cpu1上都分布有线程.
注: 下面的图显示的都是LWP(线程号)。
(2) 启动模拟压测后
》先来看MySQL的对比,比较明显(此为前0.5秒)。
…….中间省略了一些,太长了。
与上面做对比(后0.5秒):
……..
》再来看Httpd,当mysql压力完了后;cpu0和cpu1都相对空闲, httpd迁移有所增加(前0.5秒)
结合上图,可以看到,httpd在压力逐渐平稳时,cpu0 和 cpu1都较忙时, 迁移比较少。
对比(后0.5秒):
从上面可以看出到,mysql 和 httpd在不做任何控制前,其线程在CPU0 和 CPU1之间的调度是非常频繁的。
5. 配置Scheduling Domains 和 taskset分别控制mysqld 和 httpd
》使用Scheduling Domains将mysqld绑定到CPU1上。
(1) mkdir /cpusets (2) echo "cpuset /cpusets cpuset defaults 0 0" >> /etc/fstab; (3) mount -a (4) mkdir /cpusets/domain1 #注:cpuset挂载后,在其下创建任意名称的目录,都将被认为是一个新调度域 # 并且,kernel会在该目录下创建调度域中的各项参数文件。 (5) echo 1 > /cpusets/domain1/cpus #cpus:用来指定该调度域中包含的CPU。 (6) echo 0 > /cpusets/domain1/mems #mems: 用来指定该调度域中能够使用的内存段。 # 注:因为VM环境并没有启用numa功能,因此只有一段内存,即0段内存. # 即: 下文原理部分中,SMP结构中单内存的情况。 (7) ps -eLo ruser,pid,lwp,psr,args |grep -E 'mysql' > t root 1555 1555 0 /bin/sh /usr/bin/mysqld_safe mysql 1657 1657 1 /usr/libexec/mysqld mysql 1657 1659 0 /usr/libexec/mysqld mysql 1657 1660 0 /usr/libexec/mysqld mysql 1657 1661 0 /usr/libexec/mysqld mysql 1657 1662 1 /usr/libexec/mysqld mysql 1657 1670 1 /usr/libexec/mysqld mysql 1657 1671 0 /usr/libexec/mysqld mysql 1657 1672 0 /usr/libexec/mysqld mysql 1657 1673 1 /usr/libexec/mysqld mysql 1657 1674 0 /usr/libexec/mysqld (8) awk '{print $3}' t >> /cpusets/domain1/tasks #指定将那个进程绑定到该域的任务列表中。
(9) 绑定后,查看线程当前所在的CPU,发现还有线程在CPU0上,不知道是否做错了?
ps -eLo ruser,pid,lwp,psr |grep -E 'mysql|1555' root 1555 1555 0 mysql 1657 1657 1 mysql 1657 1659 0 mysql 1657 1660 0 mysql 1657 1661 0 mysql 1657 1662 1 mysql 1657 1670 1 mysql 1657 1671 1 mysql 1657 1672 1 mysql 1657 1673 1 mysql 1657 1674 1
》使用taskset将httpd绑定到CPU0上。
taskset -c 0 services httpd start [root@rhel64 ~]# ps -eLo ruser,pid,lwp,psr |grep 'apache' apache 30473 30473 0 apache 30477 30477 0 apache 30479 30479 0 apache 30721 30721 0 apache 31097 31097 0 ..... 全部都已经绑定到CPU0上了。 apache 31636 31636 0 apache 31660 31660 0 apache 31667 31667 0
6. 在CentOS6.7上模拟压力后,结果如下:
》开始前
》运行中
》httpd 和 mysqld的压力基本都上来后.
从上面这些图中可以看到,taskset绑定的效果 十分明显,而scheduling Domains的效果就有点不太理想。
==================================【理论部分】===========================
Scheduling Domains 引入的背景[http://www.ibm.com/developerworks/cn/linux/l-cn-schldom/]
Scheduling Domains 是现代硬件技术尤其是多 CPU 多核技术发展的产物。
现在,一个复杂的高端系统由上到下可以这样构成:
1. 它是一个 NUMA 架构的系统,系统中的每个 Node 访问系统中不同区域的内存有不同的速度。
2. 同时它又是一个 SMP 系统。由多个物理 CPU(Physical Package) 构成。
这些物理 CPU 共享系统中所有的内存。但都有自己独立的 Cache 。
3. 每个物理 CPU 又由多个核 (Core) 构成,即 Multi-core 技术或者叫 Chip-level Multi processor(CMP) 。
这些核都被集成在一块 die 里面。一般有自己独立的 L1 Cache,但可能共享 L2 Cache 。
4. 每个核中又通过 SMT 之类的技术实现多个硬件线程,或者叫 Virtual CPU( 比如 Intel 的 Hyper-threading 技术 ) 。
这些硬件线程,逻辑上看是就是一个 CPU 。它们之间几乎所有的东西都共享, 包括 L1 Cache,
甚至是逻辑运算单元 (ALU) 以及 Power 。
注:
SMT(Simultaneous Multithreading:同步多线程):它的基本结构实现原理:在一个时钟周期内向功能部件发送多个线程的指令,以提高线程对功能部件的利用率; Intel的研究人员基于SMT技术设计了超线程(Hyper-Threading)技术,超线程技术使一颗物理CPU,从程序运行角度上看,可呈现多个逻辑CPU的效果.
MCH(Memory Controller Hub)相当于北桥芯片。INTEL从815时就开始放弃了南北桥的说法 MCH 是内存控制器中心 ,负责连接CPU/PCI/AGP/PCIEXPRESS总线各内存,还有ICH(I/O controller hub)即输入/输出控制中心,相当于南桥芯片,负责IDE和I/O设备等。
内存控制器(Integrated Memory Controller)简称IMC,它是MCH中用来访问内存的组件,它显著的降低了内存延迟,提升性能,配合QPI(快速通道技术)使CPU读写巨量数据到内存的速度大大提高.
IOH(Input Output Hub)也就是传统意义上部分南桥的功能,通过QPI总线与CPU相连,下方使用DMI总线连接南桥ICH芯片,主要负责I/O总线的传输,它同时提供了很多PCI-E 2.0总线连接作为标准的I/O接口。因为Intel的CPU已经把内存控制器(MCH)总线集成进了CPU,也就是说把传统意义上的北桥做进了CPU里。并不是MCH变成了IOH,而是MCH已经不需要存在在主板上了。IOH的功能已经逐渐整合到CPU中。
在上述系统中,最小的执行单元是逻辑 CPU,进程的调度执行也是相对于逻辑 CPU 的。
在这样复杂的系统,调度器要解决的一个首要问题就是如何发挥这么多 CPU 的性能,使得负载均衡,
不存某些 CPU 一直很忙,进程在排队等待运行,而某些 CPU 却是处于空闲状态。
但是在这些 CPU 之间进行 Load Balance 是有代价的,比如对处于两个不同物理 CPU 的进程之间进行负载平衡的话,将会使得 Cache 失效, 造成效率的下降,而且过多的 Load Balance 会大量占用 CPU 资源。
还有一个要考虑的就是功耗 (Power) 的问题。一个物理 CPU 中的两个 Virtual CPU 各执行一个进程,显然比两个物理 CPU 中的 Virtual CPU 各执行一个进程节省功耗。因为硬件上可以实现一个物理 CPU 不用时关掉它以节省功耗。
为了解决上述的这些问题,内核开发人员 Nick Piggin 等人在 Linux 2.6 中引入基于 Scheduling Domains 的解决方案。
注:从其他一些文章中看到 Numa调度域是塔尖,上面的Numa-domain是Numa的节点域,它们包含在Numa调用域中,
这是超大型CPU阵列中才可能有的,每一层调度的成本逐级增加,也逐级增大调度的阻力,见下面。
(1) 当系统启动时,内核会分别把每个核的两个逻辑CPU 放入一个 Scheduling Domain,这个级别的domain叫做cpu_domain。
其中每个domain包括两个 CPU groups,每个 CPU group 只有一个逻辑 CPU 。
(2) 同时每个物理 CPU 的两个核被放入一个高一级的 Scheduling Domain 。这个 domain 命名为core_domain 。
其中每个 domain 包括两个 CPU groups,每个 CPU group 有两个逻辑 CPU 。
(3) 再往上的话依次还有phys_domain(物理 CPU 放入的 domain) ;
(4) numa_domain(NUMA 中的 16 个 nodes 放入的 domain) ;
allnode_domain( 所有 NUMA 中的 node 放入的 domain) ,从而将所有 CPU 组织成一个基于 domain 的层次结构。
注:numa_domain中放入16个nodes,这16个nodes指什么?【没有完全理解.】
负载均衡算法分析(动态表现)[http://blog.csdn.net/dog250/article/details/6538240]
迁移一个进程的代价就是削弱cache的作用。 因此,只要在拥有cache的处理器之间迁移进程,势必会付出这个代价,因此在设计中必然需要一种“阻力”来尽量不做进程迁移,除非万不得已!这种“阻力”就是负载均衡原则2中的“加权”系数。
1).历史负载值的影响
为防止处理器负载曲线的上下颠簸,用历史值来加权当前值是一个不错的方式 ,也就是说,所谓的负载曲线不再基于时间点 ,而是基于时间段。 然而历史负载值对总负载的影响肯定没有当前的负载值对总负载的影响大,一个时间点的负载值随着时间的流逝,对负载均衡时总负载的计算的影响应该逐渐减小。因此Linux的负载均衡器设计了一个公式专门用于负载均衡过程中对cpu总负载的计算。该公式如下:
total_load=(previous_load*(delta-1)+nowa_load)/delta
其中delta是一个可变的系数,在linux 2.6.18中设置了3个delta,分别为1,2,4,当然还可以更多,比如高一些版本的内核中delta的取值有CPU_LOAD_IDX_MAX种,CPU_LOAD_IDX_MAX由宏来定义。比如当delta为2的时候,上述公式成为:
total_load=(previous_load+nowa_load)/2
相当于历史值占据整个load值一半,而当前值占据另一半。
2).波峰/波谷的平滑化
让历史值参与计算总负载解决了同一条负载曲线颠簸的问题,但是在负载均衡时是比较两条负载曲线同一时间点上的值 ,当二者相差大于一个阀值时,实施进程迁移。 为了做到“尽量不做进程迁移”这个原则,必须将两条负载曲线的波峰和波谷平滑掉。 如果进程迁移源cpu的负载曲线此时正好在波峰,目的cpu的负载曲线此时正好在波谷,此时就需要将波峰和波谷削平,让源cpu的负载下降a,而目的cpu的负载上升b,这样它们之间的负载差就会减少a+b,这个“阻力”足以阻止很多的进程迁移操作。
3).负载曲线平滑操作的基准
负载均衡平滑操作时需要两个值,即上述的a和b,这两个值决定了削平波峰/波谷的幅度,幅度越大,阻碍负载均衡的“力度”也就越大,反之“力度”也就越小。根据参与负载均衡的cpu的层次级别的不同,这种幅度应该不同,幸运的是,可以根据调整“负载均衡过程中对cpu总负载的计算公式”中的delta来影响幅度的大小, 这样,1)和2)就在这点上获得了统一。对于目的cpu,取计算得到的total_load和nowa_load之间的最大值,而对于源cpu,则取二者最小值。可以看出,在公式中,如果delta等于1,则不执行削波峰/波谷操作,这适用于smt的情况,delta越大,历史负载值的影响也就越大,削波峰/波谷后的源cpu负载曲线和目的cpu负载曲线的差值曲线越趋于平滑,这样就越能阻止负载均衡操作(差分算法….)。
4).自下而上的遍历方式
Linux在基于调度域进行负载均衡的时候采用的是自下而上的遍历方式,这样就优先在对cache影响最小的cpu之间进行负载均衡,同时这种均衡操作会增加本cpu的负载,反过来在比较高的调度域级别上有力的阻止了对cache影响很大的cpu之间的负载均衡。 我们知道,调度域是从对cache影响最小的最底层向高层构建的。
5).结论
随着cpu级别的提高,由于负载均衡对cache利用率的影响逐渐增大,“阻力”也应该逐渐加大,因此负载均衡对应调度域使用的delta也应该增加。 算法的根本要点是什么呢?画幅图就一目了然了,delta越大,负载值受历史值的影响越大,因此按照公式所示,只有持续单调递增 的cpu负载,在源cpu选择时才会被选中,偶然一次的高负载并不足以引起其上的进程迁移至别处,相应的,只有负载持续单调递减 ,才会引起其它cpu上的进程迁移至此,这正体现了负载以一个时间段而不是一个时间点为统计周期! 而级别越高的cpu间的进程迁移,需要的“阻力”越大,因此就越受历史值的影响,因为只要历史中有一次负载很小,就会很明显的反应在当前,同样的道理,历史中有一次的负载很大,也很容易反映在当前;反之,所需“阻力”越小,就越容易受当前负载值的影响,极端的情况下,超线程的不同逻辑cpu之间的负载计算公式中delta为1,因此它们的负载计算结果完全就是该cpu的当前负载!
结论有三:
5.1).通过“负载均衡过程中对cpu总负载的计算公式”平滑了单独cpu的负载曲线,使之不受突变的影响,平滑程度根据delta微调
5.2).通过“削掉波峰/波谷”平滑了源cpu和目的cpu负载曲线在负载均衡这个时间点的差值,尽可能阻止进程迁移,阻止程度根据delta微调
5.3).执行负载均衡的过程中,一轮负载均衡在每一层的效果需要随着级别的升高而降低,这通过自下而上的遍历方式来完成
负载均衡的配置
一.概述
值得注意的是,Linux所实现的调度域和调度组仅仅描述了一个cpu的静态拓扑和一组默认的配置, 这组默认的配置生成的原则就是在第一部分中描述的各种情况的基础上在负载均衡和cache利用率之间产生最小的对抗 , Linux并没有将这些配置定死,实际上Linux的负载均衡策略是可以动态配置的。
由于负载均衡实现的时候,对调度域数据结构对象使用了浅拷贝 ,因此对于每一个最小级别的cpu,都有自己的可配置参数,而对于所有属于同一调度域的所有cpu而言,它们有拥有共享的调度组,这些共享的信息在调度域数据结构中用指针实现,因此对于调度域参数而言,每个cpu是可以单独配置的。每个cpu都可配置使得可以根据底层级别cpu上的负载情况(负载只能在最底层级别的cpu上)进行灵活的参数配置,还可以完美支持虚拟化和组调度。
二.配置方法
目录/proc/sys/kernel/sched_domain 下有所有的最底层级别的cpu目录,比如你的机器上有4个物理cpu,每个物理cpu有2个核心,每个核心都开启了超线程,则总共的cpu数量是4*2*2=16.
注:这里我并没有理解cpuset 与 sched_domain的联系,若有知情者希望分享。
busy_factor #less balancing by factor if busy
busy_idx #忙均衡的cpu_load索引
cache_nice_tries #Leave cache hot tasks for # tries
idle_idx #空闲均衡的cpu_load索引
imbalance_pct #判断该调度域是否已经均衡的一个基准值。
min_interval #最小均衡间隔
max_interval #最大均衡间隔
newidle_idx #马上就要进入idle的cpu为了尽量不进入idle而进行负载均衡时的cpu_load索引
wake_idx #唤醒进程时将进程拉至本cpu的作用力,也是一个cpu_load索引,该索引也即本地唤醒力,对于支持多队列的网卡有很大的性能增强作用。
cache_hot_time #A进程从上次调离CPU到本次即将调入CPU的时间为b,若b小于cache_hot_time,则认为A上次的缓存信息失效的可能性最小,因此调度到当前CPU上最佳.
per_cpu_gain # CPU % gained by adding domain cpus
=======================================================
taskset : 绑定进程到某个CPU上。
Mask
# Mask:为CPU亲和性的掩码数字,转为二进制后,从低到高位,分别代表cpu0,cpu1,…,
# 位置为1则允许该进程在该CPU上运行。
# 如: 我的测试宿主机是4核CPU, 现在要将VM的vCPU绑定到cpu3上,则掩码可写为: 0x8,
# 二进制为1000, 按从低到高位,则: cpu0,cpu1,cpu2都不允许3623线程运行,仅cpu3允许.
-c 可不使用掩码表示法,直接指定CPU编号:
如:-c 0-2,7 : 表示0,1,2,7
taskset -p 0x8 3623
taskset -p -c 0-2,7 101 //将PID为101的进程绑定到0,1,2,7上。 -p :指定绑定的PID
taskset -c 0 services httpd start
===========================================================
SMP(对称多处理器)
即:一个块主板上有多个CPU插槽(Socket),每个socket可插一颗CPU。
SMP下多个CPU共享同一个内存,非常容易造成资源争用,并且若一个颗CPU为多核心的,则它还可能会存在3级缓存的资源争用。
CPU在访问内存时,它是需要花三个时钟周期的,
第一周期:CPU向内存控制器(它主要用来管理内存地址分配)发生寻址指令,由内存控制器负责返回内存地址是多少。
第二周期:CPU找到该内存地址,并根据读 或 写,来向该内存区域施加访问机制(可简单理解 读锁 或 写锁)。
第三周期:CPU完成读 或 写的操作。
NUMA(非一致性内存访问)
对称多处理器上采用共享内存的方式,效率会因资源争用而降低;若为每个CPU都分配自己专属的内存控制器 和 内存区域,并且,内存控制器与CPU封装在一起,这样不仅资源争用可以降低,并且也可提高效率。这就是下面要说的NUMA的原理:
假如当前有2颗CPU,CPU0 和 CPU1,在系统初始时有200个进程,分别被平均分配给CPU0和CPU1,运行一段时间后,CPU0上只剩下10个活动进程,而CPU1上还有90个活动进程,这严重打破了运行的平衡,这时系统会自动判断并进行rebalancing(重新平衡),这时CPU0上会被掉过来40个进程,这时CPU0和CPU1又进入平衡,但平衡只能将CPU上寄存器中数据搬移过去,但无法将内存的数据搬移到CPU0的专属内存区域(注:实际这是没有必要的),这时CPU0在运行这40个进程时,就需要跨socket才能访问到CPU1内的内存控制器,并完成CPU1的专属内存访问。而这种访问次要内存区域的方式就是NUMA.
补充: 【http://blog.csdn.net/chrysanthemumcao/article/details/9236321】
从系统架构来说,目前的主流企业服务器基本可以分为三类:SMP (Symmetric Multi Processing,对称多处理架构),NUMA (Non-Uniform Memory Access,非一致存储访问架构),和MPP (Massive Parallel Processing,海量并行处理架构)。三种架构各有特点,本文将重点聊聊NUMA。
为了了解NUMA,我这里就介绍一下NUMA与其他两种Non-NUMA的主要区别。
1.SMP(Symmetric Multi Processing)
SMP是非常常见的一种架构。在SMP模式下,多个处理器均对称的连接在系统内存上,所有处理器都以平等的代价访问系统内存。它的优点是对内存的访问是平等、一致的;缺点是因为大家都是一致的,在传统的 SMP 系统中,所有处理器都共享系统总线,因此当处理器的数目增多时,系统总线的竞争冲突迅速加大,系统总线成为了性能瓶颈,所以目前 SMP 系统的处理器数目一般只有数十个,可扩展性受到很大限制。
2.MPP (Massive Parallel Processing)
MPP则是逻辑上将整个系统划分为多个节点,每个节点的处理器只可以访问本身的本地资源,是完全无共享的架构。节点之间的数据交换需要软件实施。它的优点是可扩展性非常好;缺点是彼此数据交换困难,需要控制软件的大量工作来实现通讯以及任务的分配、调度,对于一般的企业应用而言过于复杂,效率不高。
3.NUMA(Non-Uniform Memory Access)
NUMA架构则在某种意义上是综合了SMP和MPP的特点:逻辑上整个系统也是分为多个节点,每个节点可以访问本地内存资源,也可以访问远程内存资源,但访问本地内存资源远远快于远程内存资源。它的优点是兼顾了SMP和MPP的特点, 易于管理,可扩充性好;缺点是访问远程内存资源的所需时间非常的大。
在实际系统中使用比较广的是SMP和NUMA架构。像传统的英特尔IA架构就是SMP,而很多大型机采用了NUMA架构。
> 同步多线程技术(SMT)
同步多线程技术使得每个核心可以同时执行2个线程,所以对于4核的CPU来说,就可以在每个处理器芯片
上达到最大8个逻辑处理器。
前面介绍了NUMA的很牛的架构,那目前系统层面上,软件对NUMA的支持怎么样呢?
对于NUMA架构而言,经过了几十年的发展,目前的软件支持栈已经非常完备,从底层的操作系统,
到之上的数据库、应用服务器,基本所有主流的产品均以为NUMA提供了充分的支持。
☺ 操作系统(Operating System)
目前,Windows Server 2003 和Windows XP 64-bit Edition, Windows XP等都是NUMA aware的,
而Windows Vista则有了对Numa调度的支持。所有使用2.6版本以上kernel的Linux操作系统都能够支持NUMA。
而Solaris,HP-Unix等UNIX操作系统也是充分支持NUMA架构的。
☺ 数据库(Database)
对于数据库产品来说,Oracle从8i开始支持NUMA,而之后的Oracle9i,Oracle10g,Oracle11g都能够
支持NUMA。SQL Server 2005 和SQL Server 2008均有效的提供了对NUMA的支持。
☺ 中间件服务器(Middleware)
目前业界典型的受控程序主要是Java应用和.Net应用。由于内存分配,线程调度对于应用而言是透明的,
完全是由虚拟机来处理。因此它们在NUMA环境下的性能表现主要取决于虚拟机的实现是否能充分利用到底层
操作系统对NUMA的支持。
综上所述,目前的软件栈对NUMA架构均已经作了充分的支持。那么应用软件如何支持NUMA架构呢?
在传统SMP系统上,所有CPU都以同样的方式通过一个共享内存控制器来访问内存,各CPU之间也是通过
它来进行交流,所以很容易造成拥堵。而一个内存控制器所能够管理的内存数量也是非常有限的。此外,通过唯一
的hub(原意:集线器,此意:共享方式)访问内存造成的延迟也是非常高的。
在NUMA结构下,每个计算机不再只有唯一的内存控制器,而是把整个系统分成多个节点。每个节点分别有自己
的处理器和内存。系统中所有的节点都通过全互联的方式连接。所以,每当在系统中增加新的节点,系统所能够支持
的内存和带宽都会增加,具有非常好的扩展性。
NUMA的内存组织
在NUMA系统中,每个CPU可以访问两种内存:本地内存(Local Memory)和远端内存(Remote Memory)。
和CPU在同一个节点的内存称为本地内存,访问延迟非常低。不和CPU在同一节点上的内存叫做远端内存,CPU需要
通过节点间互联的方式访问,所以访问延迟要比访问本地内存长。
从软件的角度来看,远端内存和本地内存是以同样的方式访问的。理论上讲,NUMA系统可以被软件视为与
SMP同样的系统,不区分本地和远端内存。但是如果追求更好的性能,这个区别还是需要被考虑的。
经实验,对于常规的内存操作,如清空(Memset),块复制(Memcpy),流读写(Stream),指针追溯
(Pointer Chase)等操作来说,本地内存的访问速度要远远优于远端内存。由于 NUMA 同时使用本地内存和远端内存,
因此,访问某些内存区域的时间会比访问其他内存区域的要长。
本地和远端内存通常是相对于进程而言的,如上图,A和B分别运行在节点0上的Core0和Core1上,它们访问的内存
就是本地内存,而C需要的内存比较多,假设节点1端的本地内存不够用,这时设置C进程可以使用节点0端的内存,这时
C访问节点0端的内存就称为远端内存。访问远端内存的开销与访问本地内存的开销比率称为 NUMA 比率。如果
NUMA 比率为 1,则它是对称多处理 (SMP)。比率越高,访问其他节点内存的开销就越大。不支持 NUMA 的 应用程序
有时在 NUMA 硬件上的执行效果非常差。由于访问本地内存和远端内存的开销是有区别的,所以在NUMA模式下,
如果每个线程更多的是访问本地内存,那么性能相比而言会有一定提升。
NUMA策略,即如何更好的利用NUMA来给咱们干活:
为描述在NUMA架构下针对内存访问的优化,我们可以引入NUMA策略的概念。
NUMA策略(NUMA Policy)即是指在多个节点上合理的进行内存分配的机制。对于不同软件设计要求,
策略的目标可能会不同:有一些设计可能强调低延迟访问,另一些则可能更加看重内存的访问带宽。
对于强调低延迟访问的设计,基本的分配方式就是尽量在线程的本地内存上为其进行分配, 并尽量让
线程保持在该节点上。这称为线程的节点亲和性(Node affinity)。这样既充分利用了本地内存的低延迟,
同时也能有效降低节点间的通信负担。
NUMA架构的一个优势是,即便是在拥有大量CPU的大规模系统中,我们也可以保证局部内存访问的低延迟。
通常来讲,CPU的处理速度是远大于内存的存取速度的。在读写内存时,CPU常常需要花大量的时钟周期来等待。
降低内存访问的延迟因而能够有效的提升软件性能。
另外,为SMP设计的操作系统通常会有缓存亲和性(Cache Affinity) 的优化措施。
缓存亲和性机制即:让数据尽量长时间的保留在某一CPU的缓存中,而非来回在多个CPU的缓存里换来换去。
简单说:A在CPU0的core1上运行,时间片用完了,被调入内存睡眠,当A在次被导入CPU上运行时,缓存亲和性就
会尽可能将A还调度到CPU0的core1上运行,以便A在core1上执行时,可以利用上次缓存在core1的寄存器中的数据.
这一机制显然是和NUMA系统尽量利用本地内存的策略是一致的,有利于面向SMP系统的程序向NUMA架构移植。
缓存亲和性机制 与 NUMA系统的节点亲和性是有区别的:
(1) 同一个节点间多个核心或每核心的线程间迁移并不影响该线程的节点亲和性;
(2) 当线程被迫迁移到其他节点时,他所拥有的内存是不跟着迁移且仍留在原处。此时,本地内存就变成远端内存了,
对它的访问既慢又占用节点间通信带宽。相对的,线程在迁移之后能够以较小的代价迅速建立起新的缓存,
并继续在新CPU上体现缓存的亲和优势。 因此,NUMA系统对于节点亲和性的依赖更大。
(3) OS调度器默认是将CPU利用率作为优化依据,而非节点亲和性,因为频繁访问远端内存的性能开销相对于让
CPU空闲的性能开销小.若特定应用系统的性能受内存访问的影响远大于CPU的利用率,此时程序员或者管理员
则可采用特别的NUMA策略来强调节点的亲和性,从而提升性能。
另外, 尽管大部分应用会因为优化响应时间而收益,还有一部分应用则对内存带宽比较敏感。为了提升内存带宽,NUMA架构下的多个内存控制器可以并行使用。这类似于RAID阵列通过并行处理磁盘IO来提升读写性能。通过适当的软件或者硬件机制,NUMA架构可以使内存控制单元在各个内存控制器上交替的分配内存。这意味着分配得到的连续内存页面会水平地分布到各个节点上。当应用程序对内存进行流式读写时,各个内存控制器的带宽就相当于累加了。此机制获得性能提升决定于NUMA架构的实现。对于远端内存访问延迟严重的架构,该提升往往会比较明显。在一些NUMA系统中,系统硬件本身提供了节点交织分配机制;而在没有硬件提供节点交织的系统中,可由操作系统来实现该机制。
NUMA相关命令:【在RHEL6.4以后,才有这些命令】
numastat : 是获取NUMA内存访问统计信息的命令行工具。对于系统中的每个节点,内核维护了一些有关NUMA分配状态的统计数据。numastat命令会基于节点对内存的申请,分配,转移,失败等等做出统计,也会报告NUMA策略的执行状况。这些信息对于测试NUMA策略的有效性是非常有用的。
numa_hit : CPU访问专属内存的命中的次数。
numa_miss : CPU访问专属内存时未命中的次数。
numa_foreign : CPU的专属内存被外部CPU使用次数(即: CPU0的专属内存,被CPU1使用的次数).
选项:
-p <PID> or <Pattern> : 查看某个PID进程是否跨越了多个专属内存区域。
注:若一个进程跨越了多个专属内存区域,最好是将其绑定到一颗CPU上。
-s <node#> : 显示单个节点和汇总信息。【注:它主要用来排序】
numactl : 是设定进程NUMA策略的命令行工具。对于那些无法修改和重新编译的程序,
它可以进行非常有效的策略设定。Numactl使管理员可以通过简单的命令行调用来设定进程的策略,
并可以集成到管理脚本中。【这是临时生效的CPU绑定方式。】
Numactl的主要功能包括:
1. 设定进程的内存分配基本策略
2. 限定内存分配范围,如某一特定节点或部分节点集合
3. 对进程进行节点或节点集合的绑定
4. 修改命名共享内存,tmpfs或hugetblfs等的内存策略
5. 获取当前策略信息及状态
6. 获取NUMA硬件拓扑
#下面是使用numactl设定进程策略的实例: # 在节点0上的CPU运行名为program的程序,并且只在节点0,1上分配内存。 # --cpunodebind=<node#> or -N <node#> : 将某个command与CPU中指定node进行绑定。 # --membind=<node#>[,<nodes#>,...] or -m <nodes#> :仅允许给该App分配指定节点的内存. # 注: 若每个节点上有多个逻辑CPU的系统上,其逻辑CPU编号的定义顺序可能会不同。 numactl --cpunodebind=0 --membind=0,1 program #下面是使用numactl更改共享内存段的分配策略的实例: # 对命名共享内存interleaved进行设置,其策略为全节点交织分配,大小为1G。 # --interleave=[all |<nodes#>] : 交叉在指定节点上分配内存 或 在全部节点上交叉分配内存。 numactl --length=1G --file=/dev/shm/interleaved --interleave=all 其它参数: --show : 查看Numa当前的策略信息。 --physcpubind=<cpu#> or -C <cpu#> : 将进程和物理CPU完成绑定。 numad # 它是一个用户空间进程,可提供策略,可自动根据观察每个CPU节点上进程的运行状况, # 来自动将进程关联到指定CPU上,并将该CPU绑定到指定node上,它实现了自我监控 和 # 优化管理。是否要使用它可以根据numastat中的numa_miss来观察。 # 它可以只监控指定的进程,并且可指定多久(最长 和 最短)观察一次 和 观察级别。
原创文章,作者:Wn1m,如若转载,请注明出处:http://www.178linux.com/15797