最近在做一些性能測試的事情,首要前提是控制住 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