原文首发于 TechNova: 从μs到ns:基于AVX指令集的次世代撮合引擎向量化革命
承接页(解决方案):https://technologynova.org/solution/
prices/qty/ids 分开存放,确保能一次加载多笔价格进向量寄存器。典型撮合是“遍历订单簿 → 判断是否价格可成交 → 计算成交量 → 更新状态”。当订单簿深度很大(上千/上万档)时,热路径会被三类问题持续骚扰:
if (price_match) 的结果对 CPU 来说并不“可预测”,流水线频繁被冲刷。如果你已经做过对象池、避免频繁分配、把部分结构改成数组、把序列器与撮合单线程绑定等“常规低延迟套路”,那下一步通常就是:别再幻想 SISD 能带来数量级收益,改用数据并行(SIMD)。
SIMD(Single Instruction, Multiple Data)做的事很朴素:用一条指令对多个数据执行同一操作。
在 AVX2 下,一个 256-bit 寄存器可以同时放 4 个 double 价格;一条比较指令就能一次算出 4 个“是否可成交”。
更关键的是:把“每次循环一个 if”变成“每批 4 个结果的位掩码”,能显著减少分支与循环次数,把热路径更像是在做“批处理扫描”, CPU 的前端(取指/分支/流水)压力更小。
原文强调了一个很现实的结论:树/链表是 SIMD 的天敌。要向量化,你需要把核心数据改成能连续加载的布局。 两种常见布局:
Order{price,qty} 的数组。写起来舒服,但想一次加载 4 个 price 时,内存并非连续(夹着 qty 等字段)。prices[]、qtys[]、ids[] 分开存。此时 4 个 price 在内存里紧挨着,适合 _mm256_load_pd。
同时要注意对齐(AVX2 典型是 32 字节对齐)。工程上通常会使用 alignas + 对齐分配器来保证 vector 的底层地址满足要求。
向量化版本的核心步骤可以概括为:
movemask 把比较结果变成整数 bitmask(例如 0b0111 表示前 3 个匹配)。这个结构的好处是:把“可并行的比较”交给 SIMD,剩下“必须串行的状态更新”由掩码驱动。 你不会得到 100% 的 4 倍加速,但通常能在热路径上明显降低比较与分支开销,尤其对 P99 很友好。
如果你的撮合引擎已经做完“应用层常规优化”,但 P99/P999 仍被 cache miss 与分支拖累,可以按“三段式”评审落地路线: 1) 先把订单簿改成连续内存布局(数组/分块/SoA); 2) 再把最热的比较/扫描环节向量化(AVX2/AVX-512); 3) 最后用基准与回归测试守住正确性与尾延迟。 更系统的落地路径与架构咨询可参考: https://technologynova.org/solution/