带字节分隔符的可变大小字段的快速 SIMD 提取
Posted
技术标签:
【中文标题】带字节分隔符的可变大小字段的快速 SIMD 提取【英文标题】:Fast SIMD extraction of variable-sized fields with byte delimiter 【发布时间】:2017-07-21 16:32:35 【问题描述】:假设我们要从由可变长度字段组成的输入流中提取字段。我们所知道的是每个字段的最大宽度,并且每个字段都以一个值为1
的字节结尾。我们希望将打包的字段提取为固定格式,其中每个字段都有其最大宽度(如果输入字段小于最大值,则填充零)。
每个字段的最小宽度为一个字节。
例如,我们期望接收两个字段的值。第一个的最大宽度为 3 个字节,第二个的最大宽度为 2 个字节。
假设我们有一个输入向量 X, 1, 1,所以我们知道第一个字段的值为 X, 1,第二个字段的值为 1。所以在这种情况下,结果向量应该等于 0, X, 1, 0, 1。
或者,我们有一个输入向量 1, 1,所以结果向量应该等于 0, 0, 1, 0, 1。
我想我知道一种使用查找表的方法。问题是如果我们决定一次处理超过 64 位,我们最终会得到太大的查找表。
【问题讨论】:
我不太确定我是否理解您要完成的工作,但似乎可以使用permutation instructions 序列来完成。 问题是输入向量的形状可以不同。对于上面的例子,它可以是 1, 1, X, 1, 1, X, X, 1, 1, 1, X, 1 等之一。所以为了使用置换我需要先生成一个掩码,但是我不知道如何有效地做。 我很困惑的是你怎么会知道向量中字节的解释是什么。您需要以某种方式将该信息传递给函数;为什么不将它作为所需的掩码传递? 每次我们在输入向量中看到“1”,我们就知道这是当前字段的最后一个字节。所以 1, 1 意味着第一个“1”进入第一个字段的最低有效字节(LSB),第二个“1”进入第二个字段的 LSB。另一方面,如果我们得到 X, 1, Y, 1,这意味着我们应该用 X, 1 填充第一个字段的两个字节,用 Y, 1 填充第二个字段的两个字节。因此,对于宽度为 N 字节的每个字段,我们应该期望输入向量中的值宽度为 [1; N]。 算法应该以最小的延迟工作并且不需要检测任何类型的错误。 【参考方案1】:一种合理的方法是使用矢量化cmp
来查找所有 1,然后将 movmskb
这些结果作为位图放入通用寄存器,然后使用该值查找 pshufb
掩码将字节扩展到基于位图的字段中。
此技术还可以免费处理“最大字段数”限制,因为该行为内置于查找表中的随机掩码中。
现在您将无法获取完整的 32 字节 ymm
寄存器将创建一个 32 位位图并直接查找它,因为它需要像 128 GB 查找表这样的东西 1,这是不可行的(或者至少会非常慢)。在实践中,您将处理一些固定数量的输出字节,以使您的表大小保持合理,例如,在 8 到 16 个字节之间。最佳值可能取决于您在紧密循环中执行此操作的次数以及对周围代码的缓存压力成本。
假设您仍然想要更快的速度。您可以查看字段长度的实际分布,如果一些“典型”排列占主导地位,您可以有一个乐观算法来获取位图,将其散列到更少的位,然后然后在第一级随机控制表中查找该值,该表仅包含“预期”字段长度的条目。同时,您执行另一个查找以验证实际完整位图是否与与第一级表关联的预期完整位图匹配。
当您在第一级表中命中时,您按照上面的方式进行,使用大的(16 或 32 字节)洗牌,否则您会退回到上面的几个较小的查找。对于期望值,散列需要类似于“完美散列”,这样才不会发生冲突。
您可以在运行时计算查找表,或者将其作为常量嵌入到二进制文件本身中。
1 ...即使您确实创建了这样一个怪物查找表,您也会遇到pshufb
在两个 16 字节通道中工作的限制,而不是整个 32-字节寄存器。
【讨论】:
谢谢。我正在考虑相同的解决方案,除了最常见模式的第二个 LUT。我会做一些基准测试并发布结果。 您也可以尝试计算洗牌掩码,而不是在向量寄存器或通用寄存器中查找它(您可以在其中使用pdep
,这有助于很多)。类似于 Peter 在 this answer 中展示的内容 - 但由于您是按字节进行计算,因此需要扩大计算解决方案(当然 LUT 解决方案也有同样的问题),这使得成本更高。
@AlexeyR。和@Bee:使用pshufb
一次处理16 个字节可能更快。这与使用 pcmpeqb / pmovmsk / pshufb 的parsing an IPv4 dotted-quad 非常相似。关于该问题的答案+cmets 有一些有趣的想法,用于压缩 LUT 以权衡额外指令与大小 + 缓存未命中。
@Peter Cordes - 这与我的建议不同吗?我将您是否甚至可以使用 32 字节宽度而不是单个通道的问题留到一边,因为作为第一步,您确实需要了解如何生成 shuffle 掩码。如果您必须在每个通道的大表中进行查找,并且您没有一次处理 32 个字节,那么可能 16 个字节就可以了。另一方面,如果您找到一种有效生成掩码的方法,尝试一次处理 32 个字节有助于减轻存储和洗牌端口的压力。
是的,同意。我主要只是想评论链接到pshufb
以获得 IPv4 答案,它有一些实际的工作代码来做几乎完全一样的事情:零填充到 4B(然后是一些不错的 pmadd 小数位值)。以上是关于带字节分隔符的可变大小字段的快速 SIMD 提取的主要内容,如果未能解决你的问题,请参考以下文章