第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亲和性是怎么分配的:
这个分配方案是我在实际项目中用过的。行情处理线程独占0-3号核心,交易执行线程独占4-5号核心,风控和监控线程放在6-7号核心。为什么要这么分?
- 行情处理:IO密集,需要快速响应,独占核心避免被其他线程打断
- 交易执行:计算密集,需要稳定的CPU缓存,不能频繁切换
- 风控监控:优先级相对低一些,但不能没有,放在最后两个核心
我曾经遇到过一个问题:行情线程和交易线程绑在同一个核心上,结果行情爆发时交易执行被卡住,错过了最佳下单时机。后来分开绑定,问题就解决了。
避坑指南
用taskset这么多年,踩过不少坑。分享几个常见的:
坑1:超线程核心编号
有些CPU开启了超线程,一个物理核心对应两个逻辑核心。比如核心0和核心1其实是同一个物理核心。如果你把两个计算密集的线程绑在0和1上,它们会抢资源,性能反而下降。
我曾经犯过这个错,后来用 lscpu -e 查看核心拓扑,才搞清楚哪些是物理核心、哪些是逻辑核心。
建议:绑定前先用 lscpu -e 或 cat /proc/cpuinfo 确认核心布局。对于量化交易,我通常只绑定物理核心,跳过超线程的逻辑核心。
坑2:绑定后性能反而下降
有时候绑定了核心,性能反而变差了。为什么?因为操作系统原本可以在多个核心间动态调度,你把它绑死了,反而失去了灵活性。
我的做法是:先不绑定,用perf工具观察进程的运行情况,找到瓶颈后再针对性绑定。不要一上来就全绑死。
总结一下
taskset这个工具,说简单也简单,说复杂也复杂。简单的是它的用法——就几个参数,几分钟就能上手。复杂的是你得知道什么时候该绑、绑到哪个核心、绑多少个核心合适。
我个人觉得,对于量化交易系统来说,CPU亲和性绑定是性价比很高的优化手段。不需要改代码,不需要复杂的配置,一条命令就能让延迟降低不少。但前提是——你得理解你的程序是怎么跑的,哪些线程是关键的,哪些核心是空闲的。
嗯,今天就先聊到这儿。下一章咱们会深入讲讲CPU亲和性的底层原理,看看位掩码到底是怎么工作的,以及如何用sched_setaffinity系统调用在代码里直接控制亲和性。