一位法国研究生昨天通过电子邮件联系了我,提出了以下问题。考虑具有行注释的格式,例如 TOML :当遇到“#”字符时,该行的其余部分将被省略。让我们看一个例子:
[ build-system ] # 用这个来表示项目的名称 需要= [ “ setuptools>=61.0 ” ] build-backend = " setuptools.build_meta " [工具.setuptools ] 包目录= { " " = " pylib " } packages = [ " gyp " , " gyp.generator " ] # 看起来不错 # 文件结尾
带有’#’字符的行有注释。
我们希望以纯矢量化的方式处理这种情况,就像我们在快速 JSON 解析器simdjson 中所做的那样。也就是说,我们希望尽可能避免分支。
我们可以构造位图来指示行尾的位置(检查字符’\n’)和’#’的位置。实际上,我们将所有块(例如 64 个字符)转换为两个 64 位字。 64 位字中的每一位对应一个字符。当且仅当匹配字符为 ‘\n’ 时,第一个 64 位字中的位设置为 1,第二个 64 位字中的情况类似。这些词对是“位集”。可以使用SIMD 指令或其他方式有效地计算它们。
从这些位集中,我们可以计算位集的掩码(一个 64 位字的流,每个字对应 64 个字符的块),如果且只有字符被注释掉,相应的位设置为 0。
实际上,将两个位集流(用于行尾和哈希)视为大整数,我们只需要从行尾“大整数”中减去哈希“大整数”。然后我们需要确保删除所有哈希值,并放回行尾。没有干净的方法来减去大整数,但是像 GCC 和 LLVM/clang 这样的编译器有用于此目的的内部函数 (__builtin_usubll_overflow)。它非常丑陋并且需要一个溢出变量,但它编译为高效代码。您可以在 Visual Studio 中使用 Microsoft intrinsics 实现几乎相同的效果(留给读者作为练习)。
布尔溢出= 1 ; 对于( size_t i = 0 ; i < b . line_end . size ( ) ; i + + ) { // 我们从 b.hash 中减去 b.line_end,并进行溢出处理。 overflow = __builtin_usubll_overflow ( b . hash [ i ] , 湾。 line_end [ i ] +溢出, & b 。评论[ i ] ) ; 湾。评论[ i ] & = ~ b 。散列[我] ; // 当有多个#时, //我们想删除它。 湾。评论[我] | = b 。 line_end [ i ] ; // 我们想保留行起始位。 }
从那里开始,我能够使用矢量化(无分支)例程编写一个函数来修剪(删除)输入文件中的注释。我的 C++ 代码目前只有 ARM NEON 的快速路径(否则,有回退),但它可以工作。我假设有 Linux 或 macOS。
如果您愿意,请查看完整代码。它只是一个原型,即使在 ARM NEON 上也不会特别快。为了让它运行得更快,我应该避免对整个位集索引进行不必要的具体化:不需要映射出整个内容。我们可以逐块处理并避免内存分配。
此外,我没有处理在字符串中找到的“#”字符。我想他们应该被省略。
我认为这是一个令人信服的原理证明演示,您可以从一段代码中识别并可能删除行注释。我邀请贡献和努力将其变成有用的东西!
原文: https://lemire.me/blog/2023/04/26/vectorized-trimming-of-line-comments/