• 利用 cgroup 的 cpuset 控制器限制进程的 CPU 使用


    最近在做一些性能测试的事情,首要前提是控制住 CPU 的使用量。最直观的方法无疑是安装 Docker,在每个配置了参数的容器里运行基准程序。

    对于计算密集型任务,在只限制 CPU 的需求下,直接用 Linux 原生的 cgroup 功能来限制 CPU 使用无疑是最方便的。

    本文简要说明如何使用 cgroup 的 cpuset 控制器限制进程只使用某几个 CPU,更准确的说是某个几个逻辑核。

    1. 查看 CPU 配置

    常用的配置可以用如下 BASH 命令查看。

    cat /proc/cpuinfo | grep "physical id" | sort | uniq # 查看物理 CPU 数量
    cat /proc/cpuinfo | grep "cores" | uniq # 查看每块 CPU 的核心数
    cat /proc/cpuinfo | grep "processor" | wc -l # 查看主机总的逻辑线程数

    特别地,启用了超线程的话,每个 CPU 物理核心会模拟出 2 个线程,也叫逻辑核。判断方式如下:

    是否开启超线程 = 物理 CPU 数量 * 每块 CPU 核心数 / 总逻辑线程数 == 2

    2. 什么是 NUMA

    这里提到一个概念叫 NUMA,主机板上如果插有多块 CPU 的话,那么就是 NUMA 架构。每块 CPU 独占一块面积,一般都有独立风扇。

    一个 NUMA 节点包含了直连在该区域的 CPU、内存等硬件设备,通信总线一般是 PCI-E。由此也引入了 CPU 亲和性的概念,即 CPU 访问同一个 NUMA 节点上的内存的速度大于访问另一个节点的。

    执行以下命令,以查看本机的 NUMA 结构。

    numactl --harware
    # available: 2 nodes (0-1)
    # node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
    # node 0 size: 127834 MB
    # node 0 free: 72415 MB
    # node 1 cpus: 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
    # node 1 size: 128990 MB
    # node 1 free: 68208 MB
    # node distances:
    # node   0   1
    #   0:  10  21
    #   1:  21  10

    一个 NUMA 节点包括一个内存节点和属于同一块 CPU 的若干个逻辑核,请记住它们的编号,将在配置 cpuset 中用到。

    在此解释下“node distance”,访问本节点的内存的通信成本是常量值 10,操作系统以此基准来量化访问其他 NUMA 节点上内存的代价。

    3. 创建 cgroup 并配置资源使用

     内核版本较高(>=2.6.24)的 Linux 发行版都内置了 cgroup,可以执行以下命令验证一下。

    mount | grep cgroup
    # tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
    # cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)
    # cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
    # cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
    # cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
    # cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
    # cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
    # cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
    # cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
    # cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
    # cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
    

    如果没有的话,也可以执行一段简单的脚本呢,来一次性挂载齐全。

    cgroot="${1:-/sys/fs/cgroup}"
    subsys="${2:-blkio cpu cpuacct cpuset devices freezer memory net_cls net_prio ns perf_event}"
    mount -t tmpfs cgroup_root "${cgroot}"
    for ss in $subsys; do
      mkdir -p "$cgroot/$ss"
      mount -t cgroup -o "$ss" "$ss" "$cgroot/$ss"
    done
    

    cgroup 针对每一种资源都提供了相应的控制器来进行配置,在 Linux 中以文件系统的形式呈现。本文只涉及进程在物理核上的放置,因此来看一下 cpuset 目录下有什么。

    mkdir /sys/fs/cgroup/cpuset/tiger # 创建一个控制组,删除用 rmdir 命令
    ls /sys/fs/cgroup/cpuset/tiger
    # cgroup.clone_children  cpuset.memory_pressure
    # cgroup.procs           cpuset.memory_spread_page
    # cpuset.cpu_exclusive   cpuset.memory_spread_slab
    # cpuset.cpus            cpuset.mems
    # cpuset.effective_cpus  cpuset.sched_load_balance
    # cpuset.effective_mems  cpuset.sched_relax_domain_level
    # cpuset.mem_exclusive   notify_on_release
    # cpuset.mem_hardwall    tasks
    # cpuset.memory_migrate

    如果要使用 cpuset 控制器,需要同时配置 cpuset.cpus 和 cpuset.mems 两个文件(参数)。这两个文件接受用短横线和逗号表示的区间,如“0-7,16-23”。如果对应的资源不存在,那么写入的时候会报错。

    不建议直接在控制器的根目录下配置,通过创建子目录的形式可以同时维持多个控制器。执行如下命令,限制 tiger 控制组下所有进程只能使用逻辑核0和1。

    echo "0-1" > /sys/fs/cgroup/cpuset/tiger/cpuset.cpus
    echo 0 > /sys/fs/cgroup/cpuset/tiger/cpuset.mems
    

    对于 cpuset.mems 参数而言,每个内存节点和 NUMA 节点一一对应。如果进程的内存需求量较大,可以把所有的 NUMA 节点都配置进去。这里就用到了 NUMA 的概念。出于性能的考虑,配置的逻辑核和内存节点一般属于同一个 NUMA 节点,可用“numactl --hardware”命令获知它们的映射关系。

    4. 验证效果

    在 cpuset 的所有配置文件中,tasks 和 cgroups.procs 是用来管理控制组中的进程的。执行以下命令,把当前会话加入刚刚创建的控制组里,本会话发起的所有命令(子进程)都会收到 cpu 使用的约束。

    echo $$ > /sys/fs/cgroup/cpuset/tiger/cgroup.procs # 写入当前进程编号
    

    两个配置项基本是等价的,但有一小点不同。操作系统以线程为调度单位,将一个一般的 pid 写入到 tasks 中,只有这个 pid 对应的线程,以及由它产生的其他进程、线程会属于这个控制组。而把 pid 写入 cgroups.procs,操作系统则会把找到其所属进程的所有线程,把它们统统加入到当前控制组。

    进程在加入一个控制组后,控制组所对应的限制会即时生效。启动一个计算密集型的任务,申请用 4 个逻辑核。

    stress -c 4 &
    # [1] 2958521
    # stress: info: [2958521] dispatching hogs: 4 cpu, 0 io, 0 vm, 0 hdd
    

    观察 CPU 的使用情况来验证效果,只有编号为 0 和 1 的两个逻辑核在工作,用户态的比例高达 100%。

    top # 在列表页按数字 1 键,切换到 CPU 看板
    # Tasks: 706 total,   3 running, 702 sleeping,   0 stopped,   1 zombie
    # %Cpu0  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    # %Cpu1  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    # %Cpu2  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    # %Cpu3  :  0.0 us,  1.7 sy,  0.0 ni, 98.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
  • 相关阅读:
    jms学习笔记
    UML类图与类的关系详解
    javolution学习--介绍
    Eclipse中10个最有用的快捷键组合
    [疯狂Java]JDBC:PreparedStatement预编译执行SQL语句
    Oracle OLAP 与 OLTP 介绍
    DRDS SQL兼容性
    DRDS 概述
    跨时代的分布式数据库 – 阿里云DRDS详解(转)
    java.net.UnknownHostException: www.terracotta.org
  • 原文地址:https://www.cnblogs.com/shishaochen/p/9735114.html
Copyright © 2020-2023  润新知