Cgroup - 从 CPU 资源隔离说起(二)

2015-12-28 22:40:03 +08:00
 jerry017cn

针对 CPU 核心进行资源隔离

针对 CPU 核心进行隔离,其实就是把要运行的进程绑定到指定的核心上运行,通过让不同的进程占用不同的核心,以达到运算资源隔离的目的。其实对于 Linux 来说,这种手段并不新鲜,也并不是在引入 cgroup 之后实现的,早在内核使用 O1 调度算法的时候,就已经支持通过 taskset 命令来绑定进程的 cpu 核心了。

好的,废话少说,我们来看看这在 cgroup 中是怎么配置的。

其实通过刚才的 /etc/cgconfig.conf 配置文件的内容,我们已经配置好了针对不同的组占用核心的设置,来回顾一下:

group zorro {
    cpuset {
        cpuset.cpus = "1,2";
    }
}

这段配置内容就是说,将 zorro 组中的进程都放在编号为 1 , 2 的 cpu 核心上运行。这里要说明的是, cpu 核心的编号一般是从 0 号开始的。 24 个核心的服务器编号范围是从 0-23.我们可以通过查看 /proc/cpuinfo 的内容来确定相关物理 cpu 的个数和核心的个数。我们截取一段来看一下:

[root@zorrozou-pc ~/test]# cat /proc/cpuinfo
processor   : 23
vendor_id   : GenuineIntel
cpu family  : 6
model       : 63
model name  : Intel(R) Xeon(R) CPU E5-2620 v3 @ 2.40GHz
stepping    : 2
microcode   : 0x2b
cpu MHz     : 2599.968
cache size  : 15360 KB
physical id : 1
siblings    : 12
core id     : 5
cpu cores   : 6
apicid      : 27
initial apicid  : 27
fpu     : yes
fpu_exception   : yes
cpuid level : 15
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm ida arat epb xsaveopt pln pts dtherm tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid
bogomips    : 4796.38
clflush size    : 64
cache_alignment : 64
address sizes   : 46 bits physical, 48 bits virtual
power management:

其中*processor : 23*就是核心编号,说明我们当前显示的是这个服务器上的第 24 个核心,*physical id : 1*表示的是这个核心所在的物理 cpu 是哪个。这个编号也是从 0 开始,表示这个核心在第二个物理 cpu 上。那就意味着,我这个服务器是一个双物理 cpu 的服务器,那就可能意味着我们的系统时 NUMA 架构。另外还有一个要注意的是*core id : 5*这个子段,这里面隐含着一个可能的含义:你的服务器是否开启了超线程。众所周知,开启了超线程的服务器,在系统看来,一个核心会编程两个核心来看待。那么我们再确定一下是否开了超线程,可以 grep 一下:

[root@zorrozou-pc ~/test]# cat /proc/cpuinfo |grep -e "core id" -e "physical id"
physical id : 0
core id     : 0
physical id : 0
core id     : 1
physical id : 0
core id     : 2
physical id : 0
core id     : 3
physical id : 0
core id     : 4
physical id : 0
core id     : 5
physical id : 1
core id     : 0
physical id : 1
core id     : 1
physical id : 1
core id     : 2
physical id : 1
core id     : 3
physical id : 1
core id     : 4
physical id : 1
core id     : 5
physical id : 0
core id     : 0
physical id : 0
core id     : 1
physical id : 0
core id     : 2
physical id : 0
core id     : 3
physical id : 0
core id     : 4
physical id : 0
core id     : 5
physical id : 1
core id     : 0
physical id : 1
core id     : 1
physical id : 1
core id     : 2
physical id : 1
core id     : 3
physical id : 1
core id     : 4
physical id : 1
core id     : 5

这个内容显示出我的服务器是开启了超线程的,因为有同一个*physical id : 1*的*core id : 5*可能出现两次,那么就说明这个物理 cpu 上的 5 号核心在系统看来出现了 2 个,那么肯定意味着开了超线程。

我在此要强调超线程这个事情,因为在一个开启了超线程的服务器上运行我们当前的测试用例是很可能得不到预想的结果的。因为从原理上看,超线程技术虽然使 cpu 核心变多了,但是在本测试中并不能反映出相应的性能提高。我们后续会通过 cpuset 的资源隔离先来说明一下这个问题,然后在后续的测试中,我们将采用一些手段规避这个问题。

我们先通过一个 cpuset 的配置来反映一下超线程对本测试的影响,顺便学习一下 cgroup 的 cpuset 配置方法。

  1. 不绑定核心测试:

将 /etc/cgconfig.conf 文件中 zorro 组相关配置修改为以下状态,之后重启 cgconfig 服务:

group zorro {
    cpuset {
        cpuset.cpus = "0-23";
        cpuset.mems = "0-1";
    }
}

[root@zorrozou-pc ~]# service cgconfig restart

切换用户身份到 zorro ,并察看 zorro 组的配置:

[root@zorrozou-pc ~]# su - zorro
[zorro@zorrozou-pc ~]$ cat /cgroup/cpuset/zorro/cpuset.cpus 
0-23

zorro 用户对应的进程已经绑定在 0-23 核心上执行,我们看一下执行结果:

[zorro@zorrozou-pc ~/test]$ time ./prime_thread_zorro &> /dev/null

real    0m8.956s
user    3m31.990s
sys 0m0.246s
[zorro@zorrozou-pc ~/test]$ time ./prime_thread_zorro &> /dev/null

real    0m8.944s
user    3m31.956s
sys 0m0.247s

执行速度跟刚才一样,这相当于没绑定的情况。下面,我们对 zorro 组的进程绑定一半的 cpu 核心进行测试,先测试绑定 0-11 号核心,将*cpuset.cpus = "0-23"*改为*cpuset.cpus = "0-11"*。

请注意每次修改完 /etc/cgconfig.conf 文件内容都应该重启 cgconfig 服务,并重新登陆 zorro 账户。过程不再复述。

将核心绑定到 0-11 之后的测试结果如下:

[zorro@zorrozou-pc ~/test]$ time ./prime_thread_zorro &> /dev/null

real    0m9.457s
user    1m52.773s
sys 0m0.155s
[zorro@zorrozou-pc ~/test]$ time ./prime_thread_zorro &> /dev/null

real    0m9.460s
user    1m52.589s
sys 0m0.153s

14:52:02     CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
14:52:03     all   49.92    0.00    0.08    0.00    0.08    0.00    0.00    0.00    0.00   49.92
14:52:03       0  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
14:52:03       1  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
14:52:03       2  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
14:52:03       3  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
14:52:03       4  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
14:52:03       5  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
14:52:03       6   99.01    0.00    0.99    0.00    0.00    0.00    0.00    0.00    0.00    0.00
14:52:03       7  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
14:52:03       8  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
14:52:03       9  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
14:52:03      10  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
14:52:03      11  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
14:52:03      12    0.00    0.00    0.00    0.00    2.00    0.00    0.00    0.00    0.00   98.00
14:52:03      13    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
14:52:03      14    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
14:52:03      15    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
14:52:03      16    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
14:52:03      17    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
14:52:03      18    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
14:52:03      19    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
14:52:03      20    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
14:52:03      21    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
14:52:03      22    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
14:52:03      23    0.00    0.00    0.99    0.00    0.00    0.00    0.00    0.00    0.00   99.01

此时会发现一个现象,执行的总体时间变化不大,大概慢了 0.5 秒,但是 user 时间下降了将近一半。

我们再降核心绑定成 0-5,12-17 测试一下,就是*cpuset.cpus = "0-5,12-17"*,测试结果如下:

[zorro@zorrozou-pc ~/test]$ time ./prime_thread_zorro &> /dev/null

real    0m17.821s
user    3m32.425s
sys 0m0.223s
[zorro@zorrozou-pc ~/test]$ time ./prime_thread_zorro &> /dev/null  
real    0m17.839s
user    3m32.375s
sys 0m0.223s

15:03:03     CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
15:03:04     all   49.94    0.00    0.04    0.00    0.04    0.00    0.00    0.00    0.00   49.98
15:03:04       0  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
15:03:04       1  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
15:03:04       2  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
15:03:04       3  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
15:03:04       4  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
15:03:04       5  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
15:03:04       6    0.00    0.00    0.99    0.00    0.00    0.00    0.00    0.00    0.00   99.01
15:03:04       7    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
15:03:04       8    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
15:03:04       9    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
15:03:04      10    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
15:03:04      11    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
15:03:04      12  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
15:03:04      13  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
15:03:04      14  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
15:03:04      15   99.01    0.00    0.99    0.00    0.00    0.00    0.00    0.00    0.00    0.00
15:03:04      16   99.01    0.00    0.99    0.00    0.00    0.00    0.00    0.00    0.00    0.00
15:03:04      17  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
15:03:04      18    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
15:03:04      19    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
15:03:04      20    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
15:03:04      21    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
15:03:04      22    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
15:03:04      23    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00

这次测试的结果就比较符合我们的常识,看上去 cpu 核心少了一半,于是执行时间增加了几乎一倍。那么是什么原因导致我们绑定到 0-11 核心的时候看上去性能没有下降呢?

在此我们不去过多讨论超线程的技术细节,简单来说: 0-5 核心是属于物理 cpu0 的 6 个实际核心, 6-11 是属于物理 cpu1 的 6 个实际核心,当我们使用这 12 个核心的时候,运算覆盖了两个物理 cpu 的所有真实核心。而 12-17 核心是对应 0-5 核心超线程出来的 6 个核心, 18-23 则是对应 6-11 核心超线程出来的 6 个。我们的测试应用并不能充分利用超线程之后的运算资源,所以,从我们的测试用例角度看来,只要选择了合适核心, 12 核跟 24 核的效果几本差别不大。了解了超线程的这个问题,我们后续的测试过程就要注意对比的环境。从本轮测试看来,我们应该用绑定 0-5 , 12-17 的测试结果来参考绑定一半 cpu 核心的效果,而不是绑定到“ 0-11 ”上的结果。从测试结果看,减少一半核心之后,确实让运算时间增加了一倍。

出个两个思考题吧:

  1. 我们发现第二轮绑定 0-11 核心测试的 user 时间和绑定 0-23 的测试时间减少一倍,而 real 时间几乎没变,这是为什么?

  2. 我们发现第三轮绑定 0-5 , 12-17 核心测试的 user 时间和绑定 0-23 的测试时间几乎一样,而 real 时间增加了一倍,这是为什么?

至此,如何使用 cgroup 的 cpuset 对 cpu 核心进行资源分配的方法大家应该学会了,这里需要强调一点:

配置中*cpuset.mems = "0-1"*这段配置非常重要,它相当于打开 cpuset 功能的开关,本身的意义是用来配置 cpu 使用的内存节点的,不配置这个字段的结果将是 cpuset.cpus 设置无效。字段具体含义,请大家自行补脑。

针对 CPU 时间进行资源隔离

再回顾一下系统对 cpu 资源的使用方式--分时使用。分时使用要有一个基本本的时间调度单元,这个单元的意思是说,在这样一段时间范围内,我们将多少比例分配给某个进程组。我们刚才举的例子是说 1 秒钟,但是实际情况是 1 秒钟这个时间周期对计算机来说有点长。 Linux 内核将这个时间周期定义放在 cgroup 相关目录下的一个文件里,这个文件在我们服务器上是:

[root@zorrozou-pc ~]# cat /cgroup/cpu/zorro/cpu.cfs_period_us 
100000

这个数字的单位是微秒,就是说,我们的 cpu 时间周期是 100ms 。还有一点需要注意的是,这个时间是针对单核来说的。

那么针对 cgroup 的限制放在哪里呢?

[root@zorrozou-pc ~]# cat /cgroup/cpu/zorro/cpu.cfs_quota_us  
-1

就是这个 cpu.cfs_quota_us 文件。这里的 cfs 就是完全公平调度器,我们的资源隔离就是靠 cfs 来实现的。-1 表示目前无限制。

限制方法很简单,就是设置 cpu.cfs_quota_us 这个文件的值,调度器会根据这个值的大小决定进程组在一个时间周期内(即 100ms )使用 cpu 时间的比率。比如这个值我们设置成 50000 ,那么就是时间周期的 50%,于是这个进程组只能在一个 cpu 上占用 50%的 cpu 时间。理解了这个概念,我们就可以思考一下,如果想让我们的进程在 24 核的服务器上不绑定核心的情况下占用所有核心的 50%的 cpu 时间,该如何设置?计算公式为:

( 50% * 100000 * cpu 核心数)

在此设置为 1200000 ,我们来试一下。修改 cgconfig.conf 内容,然后重启 cgconfig :

group zorro {
    cpu {
            cpu.cfs_quota_us = "1200000";
    }
}

[root@zorrozou-pc ~]# service cgconfig restart

测试结果如下:

[zorro@zorrozou-pc ~/test]$ time ./prime_thread_zorro &> /dev/null

real    0m17.322s
user    3m27.116s
sys 0m0.266s
[zorro@zorrozou-pc ~/test]$ time ./prime_thread_zorro &> /dev/null

real    0m17.347s
user    3m27.208s
sys 0m0.260s

16:15:12     CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
16:15:13     all   49.92    0.00    0.08    0.00    0.04    0.00    0.00    0.00    0.00   49.96
16:15:13       0   51.49    0.00    0.00    0.00    0.99    0.00    0.00    0.00    0.00   47.52
16:15:13       1   51.49    0.00    0.99    0.00    0.00    0.00    0.00    0.00    0.00   47.52
16:15:13       2   54.46    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   45.54
16:15:13       3   51.52    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   48.48
16:15:13       4   48.51    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   51.49
16:15:13       5   48.04    0.00    0.98    0.00    0.00    0.00    0.00    0.00    0.00   50.98
16:15:13       6   49.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   51.00
16:15:13       7   49.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   51.00
16:15:13       8   49.49    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   50.51
16:15:13       9   49.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   51.00
16:15:13      10   48.00    0.00    1.00    0.00    0.00    0.00    0.00    0.00    0.00   51.00
16:15:13      11   49.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   51.00
16:15:13      12   49.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   51.00
16:15:13      13   49.49    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   50.51
16:15:13      14   49.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   51.00
16:15:13      15   50.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   50.00
16:15:13      16   50.51    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   49.49
16:15:13      17   49.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   51.00
16:15:13      18   50.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   50.00
16:15:13      19   50.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   49.50
16:15:13      20   50.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   50.00
16:15:13      21   50.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   50.00
16:15:13      22   50.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   50.00
16:15:13      23   50.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   50.00

我们可以看到,基本跟绑定一半的 cpu 核心数的效果一样,从这个简单的对比来看,使用 cpu 核心数绑定的方法和使用 cpu 分配时间的方法,在隔离上效果几乎是相同的。但是考虑到超线程的影响,我们使用 cpu 时间比率的方式很可能根 cpuset 的方式有些差别,为了看到这个差别,我们将针对 cpuset 和 cpuquota 进行一个对比测试,测试结果如下表:

cpu 比率(核心数) cpuset realtime cpuquota realtime
8.3%(2) 1m46.557s 1m36.786s
16.7%(4) 0m53.271s 0m51.067s
25%(6) 0m35.528s 0m34.539s
33.3%(8) 0m26.643s 0m25.923s
50%(12) 0m17.839s 0m17.347s
66.7%(16) 0m13.384s 0m13.015s
100%(24) 0m8.972s 0m8.932s

思考题时间又到了:请解释这个表格测试得到的数字的差异。

我们现在已经学会了如何使用 cpuset 和 cpuquota 两种方式对 cpu 资源进行分配,但是这两种分配的缺点也是显而易见的--就是分配完之后,进程都最多只能占用相关比例的 cpu 资源。即使服务器上还有空闲资源,这两种方式都无法将资源“借来使用”。

那么有没有一种方法,既可以保证在系统忙的情况下让 cgroup 进程组只占用相关比例的资源,而在系统闲的情况下,又可以借用别人的资源,以达到资源利用率最大话的程度呢?当然有!那就是--

8981 次点击
所在节点    Linux
2 条回复
Earthman
2015-12-28 23:12:01 +08:00
楼主更新下 markdown 吧, markdown 控制符需要加空格才能正确识别,就是那个 *XXOO* 啦。
yangchuansheng33
2020-02-21 19:54:01 +08:00
思考题想不出来啊,请大佬解答一下

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/246792

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX