0. 一次线上毛刺的追查
那天晚上10点,监控突然报警,p99延迟飙到了500ms,平时也就20ms。登录服务器一看,vmstat的cs列——也就是上下文切换次数——每秒15万次。正常?不过2万。那一刻我就知道,CPU这混蛋被切换淹死了。 上下文切换(context switch),教科书定义我就不写了。你只需要知道:当CPU从一个任务(线程/进程)跳到另一个时,它得保存当前任务的运行状态——寄存器、程序计数器、栈指针——再加载新任务的那一坨。这个过程,快的几微秒,慢的几十微秒。听着很快?积少成多,杀人于无形。
1. 切换的开销,比你想象中的大
你以为就保存几个寄存器?天真。 首先,直接开销。以x86为例,一次完整的进程上下文切换,涉及:保存通用寄存器、浮点寄存器、控制寄存器,更新内核栈、页表,刷新TLB(Translation Lookaside Buffer)。特别是TLB刷新,那是页表缓存啊,一刷,后续的内存访问全变成慢如蜗牛的内存walk。Intel的文档说,一次TLB flush可能引发几百到上千个CPU周期的延迟。实测中,一次进程切换大约3-5微秒,线程切换(同进程)会好一点,大概1-2微秒,因为共享页表。但别忘了,这是理想情况。 有一次我用perf stat去测一个频繁切换的程序,发现context-switches事件占了60%的CPU时间,还夹杂着TLB-load-misses和cache-misses。切换带来的缓存污染,才是真正的毒药。你切换出去,把L1、L2、L3缓存全弄脏了,再切回来,全得重新load。现代CPU的主频3GHz起,缓存未命中等个100个周期就够喝一壶了。换算一下:如果每微秒执行3000个周期,一次0.1%的缓存未命中率?实际上高太多了。 我们团队做过一个极限压测:两个线程,纯计算任务,在一个核上跑。如果两个线程绑核且不切换,QPS是3800。如果频繁yield让出CPU,QPS直接掉到1100。这还不是I/O密集,纯计算!切换的代价就是70%的性能蒸发。 怕不怕?
2. 落地大坑:三个几乎人人都会踩的陷阱

