线程独享CPU的方法介绍
问题陈述
有些时候我们为了获取更高的性能,不仅需要一个线程在指定的cpu上跑,还需要这个线程独享这个cpu,即其他的线程不要在这个cpu上跑。我们可以利用cgroup机制,将一组线程和一组cpu核建立双向绑定关系,即这组线程只能跑在这组cpu核上,同时这组cpu核上也只允许跑这组线程。每个线程再通过调用sched_setaffinity或类似的库函数达到独享cpu的目的。
cgroup背景
一个cgroup组包含一组具有同样的限制或者属性的进程/线程。比如一个cgroup组有4个线程,只有他们被允许访问cpu核48-51。子系统(subsystem)或者资源控制器(resource controller)是控制cgroup进程/线程的限制或者属性的内核组件。这些组件包括cpu, cpuacct, cpuset, memory, devices, net_cls, blkio, perf_event, net_prio, hugetlb, pids, rdma等。其中cpuset是允许cgroup进程/线程访问一组cpu核或者numa节点的组件。cpuset是通过sysfs来配置的,其根目录是/sys/fs/cgroup/cpuset,表示默认的cgroup组,所有的进程/线程默认都分到这个cgroup组,所有的cpu核也默认分到这个组。根目录下可以建立子目录表示子cgroup组,子目录下可以再建目录表示更低一级的组。不管是哪一级的cgroup组,其目录下都会有如下几个文件可供配置:
- cpus - cgroup进程/线程允许访问的cpu。
- mems - cgroup进程/线程允许访问的numa nodes。
- procs – cgroup进程IDs。
- tasks – cgroup的线程IDs。
创建cgroup
本节说明如何通过以下脚本来创建一个cgroup组,将允许的cpu核分配到这个cgroup组,下节则说明如何让线程加入到这个cgroup组。
- 在根目录下创建两个目录代表两个cgroup组,将我们希望独占的cpu核分配到isolated组,将其他的cpu核分配到housekeeping组。新建线程默认会加入housekeeping组,希望独占cpu的线程则通过下节描述的方法加入到isolated组。
- 假设我们有96个cpu核,2个numa node。我们将48-51这4个核分配到isolated组,将其他核分配到housekeeping组。因为isolated组的核都属于numa 0,我们可以只允许isolated组访问numa 0。
- 因为我们希望新建的进程/线程加入到housekeeping组,而只有指定的线程才能加入isolated组,所以我们关闭isolated组的sched_load_balance。isolated的上级组的sched_load_balance也需要关闭。一旦打开,子cgroup只能继承,不能更改。
- 将默认组里的进程都迁移到housekeeping组。这里要注意的是,有些内核线程必须绑在固定的核上(如下图),因此无法迁移,不过这些线程正常情况下不会对性能造成太大的影响。
配置后的目录结构如下:
指定线程加入cgroup
可以通过将线程ID写入cgroup组下的tasks文件来将该线程加入到一个cgroup组,以下是线程将自己加入到isolated组并绑核的代码片段:
备选方案
cgroup的优点是配置灵活,可以动态生效。如果线程的数目和绑定的核的关系比较固定,可以不依赖cgroup,直接通过修改内核启动的参数,重启生效。
- 在/etc/default/grub启动命令中增加isolcpus参数
GRUB_CMDLINE_LINUX=" crashkernel=512M isolcpus=48,49,50,51“
- 更新cfg 文件:
grub2-mkconfig -o /etc/grub2-efi.cfg
- 重启生效。