3、FAST协议详解:FAST编码原理与解码实战
各位好,今天我们来聊聊FAST协议。说实话,我在金融行情领域摸爬滚打这么多年,FAST协议一直是个绕不开的话题。它不是什么新协议了,但直到今天,全球主流交易所——像CME、Euronext、新加坡交易所——依然在用。为什么?因为它压缩率高,带宽省得吓人。
但FAST也有个毛病:编码复杂,解码更复杂。我当年第一次接触FAST时,看着那堆模板和操作符,头都大了。后来踩了不少坑,才慢慢摸清门道。今天我就把这些经验掰开揉碎,讲给你听。
3.1 FAST编码原理:模板、操作符、停止位
FAST的全称是FIX Adapted for Streaming。说白了,它是在FIX协议基础上做了一层二进制压缩。核心思路就三个:模板驱动、操作符控制、停止位定界。
3.1.1 模板(Template)
模板是什么?你可以把它理解成一张“字段说明书”。FAST不会像普通FIX那样每个字段都带标签名,而是提前约定好:第1个字段是Symbol,第2个字段是MDEntryType,第3个字段是MDEntryPx……解码时照着模板顺序读就行。
我习惯把模板比作“乐高图纸”。你不需要每块积木上都刻着“这是轮子”,图纸已经告诉你了。FAST模板就是这张图纸。
模板通常用XML定义。举个例子:
<template name="MDIncrementalRefresh" id="100">
<string name="Symbol" id="55" />
<uInt32 name="MDEntryType" id="269" />
<decimal name="MDEntryPx" id="270" />
<uInt32 name="MDEntrySize" id="271" />
</template>
你看,模板里定义了字段名、类型、FIX标签号。解码器拿到这个模板,就知道该按什么顺序、什么类型去解析二进制流。
3.1.2 操作符(Operator)
操作符是FAST最精妙的设计。它决定了当前字段的值怎么算。常见的有四种:
| 操作符 | 含义 | 典型场景 |
|---|---|---|
| None | 无操作,直接读取 | 变化频繁的字段,如价格 |
| Constant | 常量,不编码 | 固定值字段,如消息类型 |
| Default | 有默认值,变化时才编码 | 很少变化的字段,如交易状态 |
| Copy | 与前一条消息相同则不编码 | 逐笔数据中的重复字段 |
| Increment | 增量编码,只传差值 | 序列号、时间戳 |
嗯,这里要注意:操作符不是随便选的。选错了,要么压缩率上不去,要么解码出错。我个人习惯是:高频变化的字段用None,低频变化的用Default或Copy,单调递增的用Increment。
3.1.3 停止位(Stop Bit)
停止位是FAST里最“反直觉”的设计。它用每个字节的最高位(第8位)来表示“后面还有没有数据”。
举个例子:我要编码一个整数 300。二进制是 00000001 00101100。FAST会这样处理:
- 从低位开始,每7位一组:
0101100和0000001 - 加上停止位:第一组(低位)最高位置1(表示还有后续),第二组最高位置0(表示结束)
- 最终编码:
10101100 00000001
你看,原本2个字节就够了,但FAST用了2个字节,好像没省?别急,对于小数值,比如 127,只需要1个字节:01111111(停止位为0,表示结束)。
3.2 FAST解码流程
解码流程,说白了就是编码的逆过程。但有几个坑,我当年踩过,今天一并告诉你。
标准解码流程如下:
- 读取模板:先加载XML模板,解析出字段顺序和类型
- 初始化上下文:为每个Copy/Increment操作符准备“前值缓存”
- 逐字段解码:按模板顺序,对每个字段:
- 读取停止位,还原整数值
- 根据操作符计算实际值(如Copy则取缓存值,Increment则累加差值)
- 更新缓存(如果是Copy/Increment字段)
- 组装消息:将解码后的字段组装成FIX-like的消息结构
我画了一张流程图,帮你理清这个逻辑:
解码时有个关键点:上下文管理。Copy操作符依赖前一条消息的值,Increment依赖前一条消息的值做累加。如果上下文丢了,整条消息就废了。
3.3 性能瓶颈分析
FAST解码的性能瓶颈,我总结了三个地方:
3.3.1 停止位解析
停止位是逐字节处理的。每个字节都要检查最高位。在CPU层面,这其实是个分支预测的噩梦。因为停止位是随机的,CPU很难预测下一个字节是不是结尾。
我做过测试:停止位解析占了解码总时间的40%以上。优化思路有两个:
- 查表法:预计算所有可能的停止位组合,用查表代替逐字节判断
- SIMD加速:用SSE/AVX指令一次处理16个字节,并行检查停止位
3.3.2 模板查找
每个消息开头都有一个模板ID。解码器要根据这个ID找到对应的模板定义。如果模板数量多(比如上百个),哈希查找的开销就上来了。
我建议:用数组代替哈希表。模板ID通常是连续的整数,直接用数组索引,O(1)查找,比哈希表快一个数量级。
3.3.3 内存分配
每条消息解码后都要分配内存来存放字段值。如果频繁分配释放,GC压力很大。在Java里尤其明显。
我的做法是:对象池化。预分配一批消息对象,解码时从池里取,用完了归还。这样零GC,延迟稳定在微秒级。
好了,FAST协议的核心内容就这些。编码原理、解码流程、性能瓶颈,每个点我都结合实战经验讲了一遍。你如果正在做行情系统,这几个地方一定要重点关注。下次遇到FAST解码慢的问题,先检查停止位解析,再查模板查找,最后看内存分配——按这个顺序排查,基本能解决90%的问题。
公众号:蓝海资料掘金营,微信deep3321