第1章:taskset命令实战——CPU亲和性绑定入门

说实话,我刚开始做量化交易系统优化那会儿,对CPU亲和性这东西也没太当回事。直到有一次,我们的行情处理程序在高频交易时段出现了莫名其妙的延迟抖动,排查了三天才发现——操作系统把进程在不同核心间来回切换,缓存全给打飞了。嗯,从那以后,taskset就成了我工具箱里的常客。

今天咱们就来聊聊这个最基础、也最实用的工具——taskset。它就像一把螺丝刀,简单,但用好了能解决大问题。

taskset是什么?

说白了,taskset就是Linux系统里用来设置或查看进程CPU亲和性的命令行工具。你可以把它理解成「给进程指定工位」——告诉操作系统:这个进程,你就让它在这些CPU核心上跑,别乱动。

我个人习惯把CPU亲和性分成两种:

  • 软亲和性:系统尽量让进程在指定核心上跑,但压力大时还是会迁移
  • 硬亲和性:强制绑定,打死也不换核心

taskset默认做的是硬亲和性设置。你指定了核心,它就真的只在那上面跑。

基本用法:绑定到指定核心

先看最简单的场景——启动一个进程并绑定到某个核心。

# 绑定到CPU 0号核心
taskset -c 0 ./my_trading_program

# 绑定到CPU 2号核心
taskset -c 2 ./my_trading_program

-c参数后面跟的是核心编号。注意,核心编号从0开始。我见过有人以为从1开始,结果绑错了核心,排查半天才发现。

核心编号怎么看?

lscpu 命令就能看到你的机器有多少个核心。比如输出显示 CPU(s): 8,那核心编号就是0到7。

如果你想让一个已经在跑的进程绑定到指定核心,用 -p 参数:

# 把PID为1234的进程绑定到CPU 0
taskset -p -c 0 1234

我在项目中遇到过这种情况:交易程序已经跑起来了,发现延迟不对劲,赶紧查一下它跑在哪个核心上,然后动态调整。不用重启进程,这点很关键——重启意味着错过行情。

绑定到核心范围

有时候一个进程需要多个核心。比如我们的行情解码线程,既要处理网络数据,又要做协议解析,一个核心可能不够用。

这时候可以绑定到一个范围:

# 绑定到CPU 0到3号核心
taskset -c 0-3 ./my_trading_program

# 也可以指定不连续的多个核心
taskset -c 0,2,4 ./my_trading_program

# 混合写法也行
taskset -c 0-2,4,6-7 ./my_trading_program

你想想看,为什么需要绑定到多个核心?因为你的程序可能是多线程的。每个线程可以跑在不同的核心上,互不干扰。但要注意——taskset绑定的是整个进程,所有线程都会继承这个亲和性设置。

我的经验:对于量化交易系统,我通常把行情处理线程绑定到一组核心(比如0-3),把交易执行线程绑定到另一组核心(比如4-7)。这样行情波动不会影响交易执行,反过来也一样。

查看进程当前亲和性

这个功能我用的频率非常高。调试的时候,第一件事就是看看进程到底跑在哪个核心上。

# 查看PID为1234的进程的CPU亲和性
taskset -p 1234

# 输出示例
pid 1234's current affinity mask: 1
pid 1234's new affinity mask: 1

看到那个 affinity mask 了吗?它是一个位掩码。比如 1 表示只绑定了CPU 0,3 表示绑定了CPU 0和1(二进制是11),ff 表示绑定了所有8个核心。

说实话,位掩码看着有点费劲。所以我更喜欢用 -c 参数来查看,输出更直观:

taskset -cp 1234

# 输出示例
pid 1234's current affinity list: 0

这样一眼就能看出来——哦,这个进程现在跑在CPU 0上。

注意:查看亲和性不需要root权限,但修改别人的进程需要。如果你在调试生产环境的交易程序,记得确认权限够不够。

实战场景:量化交易中的典型用法

我给大家画个图,看看在典型的量化交易系统中,CPU亲和性是怎么分配的:

量化交易系统CPU亲和性分配示意图 物理CPU核心(0-7) 核心 0 行情解码线程 (taskset -c 0) 核心 1 行情解码线程 (taskset -c 1) 核心 2 行情解码线程 (taskset -c 2) 核心 3 行情解码线程 (taskset -c 3) 核心 4 交易执行线程 (taskset -c 4) 核心 5 交易执行线程 (taskset -c 5) 核心 6 风控计算线程 (taskset -c 6) 核心 7 监控日志线程 (taskset -c 7) 行情处理(核心0-3)→ 交易执行(核心4-5)→ 风控监控(核心6-7)

这个分配方案是我在实际项目中用过的。行情处理线程独占0-3号核心,交易执行线程独占4-5号核心,风控和监控线程放在6-7号核心。为什么要这么分?

  • 行情处理:IO密集,需要快速响应,独占核心避免被其他线程打断
  • 交易执行:计算密集,需要稳定的CPU缓存,不能频繁切换
  • 风控监控:优先级相对低一些,但不能没有,放在最后两个核心

我曾经遇到过一个问题:行情线程和交易线程绑在同一个核心上,结果行情爆发时交易执行被卡住,错过了最佳下单时机。后来分开绑定,问题就解决了。

避坑指南

用taskset这么多年,踩过不少坑。分享几个常见的:

坑1:超线程核心编号

有些CPU开启了超线程,一个物理核心对应两个逻辑核心。比如核心0和核心1其实是同一个物理核心。如果你把两个计算密集的线程绑在0和1上,它们会抢资源,性能反而下降。

我曾经犯过这个错,后来用 lscpu -e 查看核心拓扑,才搞清楚哪些是物理核心、哪些是逻辑核心。

建议:绑定前先用 lscpu -ecat /proc/cpuinfo 确认核心布局。对于量化交易,我通常只绑定物理核心,跳过超线程的逻辑核心。

坑2:绑定后性能反而下降

有时候绑定了核心,性能反而变差了。为什么?因为操作系统原本可以在多个核心间动态调度,你把它绑死了,反而失去了灵活性。

我的做法是:先不绑定,用perf工具观察进程的运行情况,找到瓶颈后再针对性绑定。不要一上来就全绑死。

总结一下

taskset这个工具,说简单也简单,说复杂也复杂。简单的是它的用法——就几个参数,几分钟就能上手。复杂的是你得知道什么时候该绑、绑到哪个核心、绑多少个核心合适。

我个人觉得,对于量化交易系统来说,CPU亲和性绑定是性价比很高的优化手段。不需要改代码,不需要复杂的配置,一条命令就能让延迟降低不少。但前提是——你得理解你的程序是怎么跑的,哪些线程是关键的,哪些核心是空闲的。

嗯,今天就先聊到这儿。下一章咱们会深入讲讲CPU亲和性的底层原理,看看位掩码到底是怎么工作的,以及如何用sched_setaffinity系统调用在代码里直接控制亲和性。


专注资料整理