ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SimdAnyof.h

SimdAnyof.h

仓库中的文件是 folly/algorithm/simd/detail/SimdAnyOf.h,它在 SimdForEach 的遍历框架上实现 SIMD 版 std::any_of:

加载一个或多个 SIMD 寄存器

对每个寄存器执行向量谓词 p

合并各 lane 的逻辑结果

只要有一个有效 lane 为 true,就提前结束

## 第 1–24 行:声明和依赖

- 第 1–15 行:Apache 2.0 许可证。
- 第 17 行:

#pragma once

防止头文件在同一翻译单元中被重复包含。

- 第 19 行:

#include <folly/CPortability.h>

提供 FOLLY_ALWAYS_INLINE。

- 第 20 行:

#include <folly/algorithm/simd/detail/SimdForEach.h>

引入刚才分析的 SIMD 区间遍历框架。它负责:
- 地址对齐;
- 首尾不完整寄存器;
- ignore_extrema;
- 主循环展开;
- 提前退出。

- 第 21 行:

#include <folly/algorithm/simd/detail/UnrollUtils.h>

提供编译期展开工具:

arrayMap
arrayReduce

- 第 23–24 行:

namespace folly {
namespace simd::detail {

进入 Folly SIMD 内部实现命名空间。

## 第 26–34 行:AnyOfDelegate 的作用

/**
* AnyOfDelegate
*
* Implementation detail of simdAnyOf
* This is a delegate to simdForEach
*/

SimdForEach 本身不知道要执行什么算法,只负责遍历 SIMD 寄存器。具体操作由 delegate 提供。

AnyOfDelegate 实现了 SimdForEach 要求的两个接口:

step(...)
unrolledStep(...)

它把通用遍历转换成 any_of 语义:

某个有效元素满足谓词 → true
所有有效元素都不满足 → false

第 32–33 行说明实现思路参考了 EVE SIMD 库。

## 第 35–36 行:delegate 模板

template <typename Platform, typename I, typename P>
struct AnyOfDelegate {

三个模板参数分别是:

- Platform:SIMD 平台抽象;
- I:迭代器或指针类型;
- P:向量谓词类型。

在当前实际调用中:

I = T*

Platform 可能是:

SimdSse42Platform<T>
SimdAvx2Platform<T>
SimdAarch64Platform<T>

它需要提供:

Platform::reg_t
Platform::logical_t
Platform::kCardinal
Platform::loada(...)
Platform::any(...)
Platform::logical_or(...)

P 通常是 lambda,例如:

[](typename Platform::reg_t x) {
return Platform::equal(x, needle);
}

注意谓词接收的是整个 SIMD 寄存器,不是单个标量。

## 第 37–38 行:构造函数

// _p to deal with a shadow warning on an old gcc
explicit AnyOfDelegate(P _p) : p(_p) {}

### _p

构造参数命名为 _p,是为了避免旧版本 GCC 的变量遮蔽警告。如果也叫 p,可能被认为遮蔽了第 59 行的成员 p。

### explicit

防止 P 被隐式转换成 AnyOfDelegate:

AnyOfDelegate delegate = predicate; // 不允许
AnyOfDelegate delegate{predicate}; // 允许

### p(_p)

将传入的谓词复制到成员变量 p。

因此当前实现一般要求谓词可复制。它没有使用:

p(std::move(_p))

所以这里明确执行复制,而不是移动。

## 第 40–45 行:处理一个寄存器

### 第 40–41 行

template <typename Ignore, typename UnrollStep>
FOLLY_ALWAYS_INLINE bool step(I it, Ignore ignore, UnrollStep) {

这是 SimdForEach 要求的单寄存器处理接口。

参数:

- it:当前 SIMD 块的起始地址;
- ignore:哪些 lane 无效;
- UnrollStep:当前模板展开位置。

Ignore 会被推导为:

ignore_none

或者:

ignore_extrema

第三个参数没有变量名,因为本实现不需要知道当前是展开中的第几个寄存器,但为了满足 delegate 接口仍然保留这个参数。

FOLLY_ALWAYS_INLINE 确保加载、谓词和归约能够合并进最终算法。

### 第 42 行

auto test = p(Platform::loada(it, ignore));

这行包含两个步骤。

第一步,加载 SIMD 寄存器:

Platform::loada(it, ignore)

loada 从 it 开始加载一个完整 SIMD 寄存器。

如果是完整块:

ignore == ignore_none{}

所有 lane 都有效。

如果是首尾部分块:

ignore == ignore_extrema{first, last}

部分 lane 可能对应数组边界之外的垃圾值。

第二步,执行向量谓词:

p(加载出来的寄存器)

谓词必须返回 Platform::logical_t,即一个 SIMD 逻辑寄存器。

例如寄存器包含:

[3, 7, 9, 7]

谓词是“是否等于 7”,则 test 概念上是:

[false, true, false, true]

在 SSE/AVX 中,这通常不是四个 C++ bool,而是一个整数 SIMD 寄存器,其中匹配 lane 的所有位为 1。

### 第 43 行

res = Platform::any(test, ignore);

把 SIMD 逻辑寄存器横向归约成一个普通 bool:

任一有效 lane 为 true → res = true
所有有效 lane 为 false → res = false

ignore 非常重要。

假设首块为:

实际区间: [7, 9]
完整加载:[7, 7, 9, 7]
ignore :前两个 lane 和最后一个 lane 无效

即使区间外 lane 满足谓词,也不能让结果变成 true。Platform::any(test, ignore) 会屏蔽这些无效 lane。

这里使用赋值而不是:

res |= ...

是安全的,因为一旦结果为 true,下一行就会要求遍历立即终止;只有当前结果为 false 时,后续块才会继续覆盖 res。

### 第 44 行

return res;

把结果同时作为 SimdForEach 的提前退出信号:

- true:已找到匹配元素,停止遍历;
- false:当前寄存器没有匹配,继续遍历。

### 第 45 行

结束 step。

## 第 47–57 行:一次处理多个寄存器

### 第 47–48 行

template <std::size_t N>
FOLLY_ALWAYS_INLINE bool unrolledStep(std::array<I, N> arr) {

这是展开主循环使用的接口。

arr 保存连续 N 个完整 SIMD 块的起始指针。例如:

arr[0] → 第一个寄存器
arr[1] → 第二个寄存器
arr[2] → 第三个寄存器
arr[3] → 第四个寄存器

只有完整块才会传给 unrolledStep,因此这里不需要 ignore_extrema。

N 通常等于 simdAnyOf 的 unrolling 参数,默认是 4。

### 第 49 行

// Don't have to forceinline - no user code dependency

这里表达的意思是,内部加载 lambda 没有用户自定义逻辑,不需要单独给 lambda 添加强制内联标记;外层 unrolledStep 和 arrayMap 本身已经强制内联。

### 第 50–52 行

auto loaded = detail::UnrollUtils::arrayMap(arr, [](I it) {
return Platform::loada(it, ignore_none{});
});

对 arr 中的每个地址执行 SIMD 加载。

假设 N == 4,概念上相当于:

std::array loaded{
Platform::loada(arr[0], ignore_none{}),
Platform::loada(arr[1], ignore_none{}),
Platform::loada(arr[2], ignore_none{}),
Platform::loada(arr[3], ignore_none{}),
};

返回的 loaded 类型大致是:

std::array<typename Platform::reg_t, N>

因为展开区间只包含完整块,所以统一传入:

ignore_none{}

这样先把加载集中起来,有利于 CPU 并行发射多个互不依赖的内存读取。

### 第 53 行

auto tests = detail::UnrollUtils::arrayMap(loaded, p);

对每个已加载的 SIMD 寄存器应用谓词 p。

概念上:

std::array tests{
p(loaded[0]),
p(loaded[1]),
p(loaded[2]),
p(loaded[3]),
};

tests 类型大致是:

std::array<typename Platform::logical_t, N>

每个元素代表一个寄存器内各 lane 的谓词结果。

注意 arrayMap 按值接收操作对象,所以成员谓词 p 在这里还可能被复制一次。

### 第 54 行

auto test =
detail::UnrollUtils::arrayReduce(tests, Platform::logical_or);

将多个 SIMD 逻辑寄存器按位“或”合并成一个逻辑寄存器。

如果:

tests[0] = [F, F, T, F]
tests[1] = [F, F, F, F]
tests[2] = [T, F, F, F]
tests[3] = [F, T, F, F]

合并后:

test = [T, T, T, F]

这里不关心匹配发生在哪个寄存器,只关心是否至少存在一个匹配。

arrayReduce 使用平衡树归约。对于 4 个值,类似:

logical_or(
logical_or(tests[0], tests[1]),
logical_or(tests[2], tests[3]));

而不是形成较长的串行依赖链:

(((tests[0] | tests[1]) | tests[2]) | tests[3])

平衡归约更利于 CPU 指令级并行。

### 第 55 行

res = Platform::any(test, ignore_none{});

把合并后的逻辑寄存器归约为一个 bool。

因为 unrolledStep 只处理完整块,所以所有 lane 都有效,使用:

ignore_none{}

这种设计还有一个性能优势:对于 N 个寄存器,只执行一次相对昂贵的横向 any/movemask 操作,而不是每个寄存器执行一次。

代价是:即使第一个寄存器已经匹配,当前整个展开组的加载、谓词和合并通常仍会完成,然后才退出。

### 第 56 行

return res;

将结果返回给 SimdForEach:

- true:停止后续遍历;
- false:继续下一展开组或尾部块。

### 第 57 行

结束 unrolledStep。

## 第 59–61 行:delegate 状态

### 第 59 行

P p;

保存用户提供的 SIMD 谓词。

例如 ContainsImpl.h 传入:

[&](typename Platform::reg_t x) {
return Platform::equal(x, needle);
}

### 第 60 行

bool res = false;

保存最终结果,默认值为 false。

这保证空区间返回 false:空区间中 step 和 unrolledStep 都不会执行,因此 res 保持初始值。

### 第 61 行

结束 AnyOfDelegate。

## 第 63–75 行:simdAnyOf 接口说明

### 第 64 行

simdAnyOf<Platform, unrolling = 4>(f, l, p);

展示调用形式。实际调用时默认模板参数不需要写成 = 4,例如:

simdAnyOf<Platform>(f, l, predicate);

或者显式指定:

simdAnyOf<Platform, 1>(f, l, predicate);
simdAnyOf<Platform, 4>(f, l, predicate);

### 第 66–67 行

它类似 std::any_of,但谓词是向量谓词:

Platform::reg_t → Platform::logical_t

区别是:

// 普通 std::any_of
bool predicate(T scalar);

// simdAnyOf
Platform::logical_t predicate(Platform::reg_t vector);

### 第 69–70 行

默认展开因子是 4。

对于简单谓词,例如“是否相等”,4 路展开通常能提高吞吐量。

对于昂贵谓词,展开 4 路可能导致:

- 寄存器压力增大;
- 代码体积增大;
- 同一批次做太多不必要工作;
- 编译器产生溢出到栈的临时数据。

此时展开因子 1 可能更合适。

### 第 72–74 行

函数被标记为 FOLLY_ALWAYS_INLINE。

这是内部构建模块,不希望最终用户直接依赖。上层通常会为具体功能建立一个非内联调用边界,例如:

containsU8
containsU16
containsU32
containsU64

内部 SIMD 模板则全部展开在这些明确边界之后。

## 第 76–81 行:simdAnyOf 主函数

### 第 76 行

template <typename Platform, int unrolling = 4, typename T, typename P>

四个模板参数:

- Platform:必须由调用者显式指定;
- unrolling:可选,默认为 4;
- T:根据 f、l 自动推导;
- P:根据谓词 p 自动推导。

例如:

simdAnyOf<SimdPlatform<std::uint8_t>, 4>(
first, last, predicate);

### 第 77 行

FOLLY_ALWAYS_INLINE bool simdAnyOf(T* f, T* l, P p) {

接收:

- f:起始指针;
- l:尾后指针;
- p:按值传入的 SIMD 谓词。

区间是标准半开区间:

[f,l)

函数没有标记 noexcept,所以如果谓词的复制或调用抛出异常,异常可以向上传播。

### 第 78 行

AnyOfDelegate<Platform, T*, P> delegate{p};

将谓词包装成 SimdForEach 能理解的 delegate。

实例化后的结构概念上是:

struct {
P p;
bool res = false;

bool step(...);
bool unrolledStep(...);
};

这里再次复制 p 进入 delegate。

### 第 79 行

simdForEachAligning<unrolling>(
Platform::kCardinal, f, l, delegate);

启动底层 SIMD 遍历。

Platform::kCardinal 表示一个 SIMD 寄存器包含多少个 T:

kCardinal = sizeof(Platform::reg_t) / sizeof(T);

simdForEachAligning 随后负责:

1. 空区间处理;
2. 将首地址向下对齐;
3. 用 ignore_extrema 处理首块;
4. 调用 step 处理少量完整块;
5. 调用 unrolledStep 处理展开组;
6. 用 ignore_extrema 处理尾块;
7. delegate 返回 true 时提前结束。

由于 delegate 按引用传入,对 delegate.res 的修改会保留到调用结束。

### 第 80 行

return delegate.res;

返回遍历结果。

可能情况:

空区间 → false
某个有效 lane 满足谓词 → true
所有有效 lane 均不满足 → false
只有区间外 lane 满足谓词 → false

### 第 81 行

结束 simdAnyOf。

### 第 83–84 行

} // namespace simd::detail
} // namespace folly

关闭命名空间。

## 与 contains 的连接

ContainsImpl.h 中这样调用:

return simdAnyOf<Platform, 4>(
haystack.data(),
haystack.data() + haystack.size(),
[&](typename Platform::reg_t x) {
return Platform::equal(x, needle);
});

数据流为:

SimdForEach
→ Platform::loada 加载多个元素
→ lambda 同时比较多个元素与 needle
→ Platform::logical_or 合并多个寄存器
→ Platform::any 判断是否至少一个 lane 相等
→ 找到后提前退出

假设一个寄存器包含 4 个元素,4 路展开时,一组最多检查 16 个元素:

寄存器 0 ─→ compare ─┐
寄存器 1 ─→ compare ─┼→ logical_or → any → bool
寄存器 2 ─→ compare ─┤
寄存器 3 ─→ compare ─┘

所以 SimdAnyOf.h 的核心职责是:把 SimdForEach 提供的“寄存器遍历能力”,包装成具有 any_of 语义的“加载—谓词—合并—归约—提前退出”流程。

返回列表