<!-- curated-deep-dive -->
`folly/algorithm/simd/FindFixed.h` 深度解读
-
源码行数:301
-
源文件 SHA-256:`0832c3c1d9f875a213e8e6910eacc210d8aa282920e30a223d3c251e4f9ef34a`
-
文档类型:人工精读版
`findFixed` 搜索编译期已知长度、总大小不超过 64 字节的整数或枚举区间。它利用静态长度在编译期选择"标量展开、单寄存器、两个重叠子区间或按首寄存器递归拆分",返回第一个匹配索引。
第 1--57 行:依赖与类型约束
第 1--39 行为许可证、标准设施、Folly movemask/Traits 及 ISA intrinsic。第 41--53 行在 `folly::detail` 定义仅接受 8/16/32/64 位整数的 `SimdFriendlyType`;注释说明使用 `is_same_v` 是为降低 concept 编译成本。第 55--57 行公开概念额外允许底层类型受支持的枚举。
第 59--81 行:公共契约
接口接收静态 extent 的 `span<const T,N>` 和可转换为 T 的 needle。第 81 行 requires 把总字节数限制为 64。动态 span 无法满足模板形态;返回 `optional<size_t>`,空值表示未找到。示例第 68 行注释把变量写成 `m0`,应为 `m1`,不影响代码。
第 87--110 行:constexpr 与标量后备
`findFixedConstexpr` 是直线线性搜索,遇首个相等值立即返回索引。它既服务 constant evaluation,也作为很短/无 SIMD 平台的运行时实现。`findFixedLetTheCompilerDoIt` 复用它;作者实测 Clang/GCC 对固定小循环可展开,更复杂写法没有收益。
第 112--159 行:寄存器宽度与分派树
最大寄存器字节数按目标为 AVX2 32、其他 x64/AArch64 16、无 SIMD 1。第 124--136 行前置声明单寄存器及两种递归组合策略。
`findFixedDispatch` 的编译期分支为:
-
`N==0`:空值;
-
`N<=2`、总字节不足 8 或无 SIMD:标量;
-
恰好 8/16/32 字节:对应寄存器实现;
-
总大小至少两个最大寄存器:先切首个最大寄存器,再递归尾部;
-
其余非规则尺寸:用两个 2 次幂子区间重叠覆盖。
全部是 `if constexpr`,最终实例只保留一个策略。
第 161--176 行:两个重叠子区间
`kRegSize=bit_floor(N)` 取不超过 N 的最大 2 次幂元素数。先搜索头部 `[0,kRegSize)`,未命中再搜索尾部 `[N-kRegSize,N)`。
两段可能重叠,但先头后尾仍保证返回全区间第一个匹配:尾段中落在重叠区域的匹配若存在,头段已经更早返回。尾段索引需加 `N-kRegSize`。
第 178--193 行:首寄存器递归拆分
按 `kMaxSimdRegister/sizeof(T)` 计算一个满寄存器的元素数。先搜索 head,未命中才递归 tail,并给尾部结果加 head 长度。这适合 32--64 字节等可由多个寄存器组成的范围,保持"第一个匹配"语义。
第 195--202 行:位图转首索引
`firstTrue` 调用 `movemask<Scalar>`。位图非零时,`countr_zero(bits)` 得到最低置位位置,再除 `bitsPerElement()` 换算 lane;为空返回 nullopt。这正确处理 x86 uint16 每 lane 两位以及 NEON 的不同位组宽度。
第 204--251 行:x86 实现
`find16ByteReg` 按 1/2/4 字节选择 `_mm_cmpeq_epi*`;没有 8 字节 SSE 分支,因此 16 字节的两个 64 位元素会在分派早期因 `N<=2` 走标量。
`find8bytes` 先 memcpy 到 uint64,随后用 `_mm_set1_epi64x` 将这 8 字节复制到 128 位两半,再复用 16-byte 比较。复制不会改变最低半部的首结果。`find16bytes` 正常未对齐加载。
AVX2 的 32-byte 实现支持 1/2/4/8 字节元素,比较后统一交给 `firstTrue`。
第 253--279 行:AArch64 实现
8-byte NEON 路径按 uint8/16/32 选择 64 位向量;64 位元素会因 N<=2 走标量。16-byte 路径覆盖 uint8/16/32/64 的 `vceqq` 比较。所有逻辑向量经公共 movemask 定位首 lane。
第 283--301 行:最终入口
若 needle 类型 U 与 T 不同,第 290--291 行只转换一次并递归,随后 T/U 相同。constant evaluation 在第 292--293 行强制走纯 constexpr 循环,不实例化 intrinsic 执行路径。
运行时第 295--297 行先用 `asSimdFriendlyUint` 把有符号整数、枚举和 span 零拷贝规范化为等宽无符号类型,再进入分派。相等比较只依赖位表示,因此转换保持结果。第 301 行关闭命名空间。
算法示例
若 N=13、元素 1 byte、寄存器上限 16 byte:`bit_floor(13)=8`,搜索 `[0,8)` 和 `[5,13)`。若 N=40 且 AVX2 可用,则先搜索 32-byte head,再递归 8-byte tail。两种组合都不会读取静态 span 之外。