【问题标题】:Use C# Vector<T> SIMD to find index of matching element使用 C# Vector<T> SIMD 查找匹配元素的索引
【发布时间】:2019-11-19 04:18:36
【问题描述】:

使用 C# 的Vector&lt;T&gt;,我们如何才能最有效地矢量化查找集合中特定元素的索引的操作?

作为约束,集合将始终是整数基元的Span&lt;T&gt;,并且最多包含 1 个匹配元素。

我想出了一个看起来不错的解决方案,但我很好奇我们是否可以做得更好。方法如下:

  1. 在每个槽中创建一个仅包含目标元素的Vector&lt;T&gt;
  2. 在输入集向量和上一步的向量之间使用Vector.Equals(),以获得在单个匹配槽中包含 1 的掩码(如果没有匹配,则仅包含 0)。
  3. 使用包含从 1 开始的索引 (1, 2, 3, 4, ...) 的预初始化向量,在该向量和上一步的掩码之间调用 Vector.Dot()。每个索引都将乘以 0,除了潜在的匹配索引,它将乘以 1。我们得到的是这些乘法的总和,即 0 或匹配元素的从 1 开始的索引。李>
  4. 如果结果为 0,则返回 -1 表示不匹配。否则,从结果中减去 1 使其从 0 开始,然后返回。

        // One-time initialized vector containing { 1, 2, 3, 4, ... }
        Vector<ushort> indexes = MemoryMarshal.Cast<ushort, Vector<ushort>>(Enumerable.Range(1, Vector<ushort>.Count).Select(index => (ushort)index).ToArray())[0];
    
        // The input set and the element to search for
        Span<ushort> set = stackalloc ushort[]{ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
        ushort element = 22;
    
        // Interpret input set as a sequence of vectors (set is assumed to have length power of two for brevity)
        var setVectors = MemoryMarshal.Cast<ushort, Vector<ushort>>(set);
    
        // Create a vector that contains the target element in each slot
        var elementVector = new Vector<ushort>(element);
    
        // Loop per vector rather than per element
        foreach (var vector in setVectors)
        {
            // Get a mask that has a 1 in the single matching slot, or only 0s
            var mask = Vector.Equals(vector, elementVector);
    
            // Get the dot product of the mask and the indexes
            // This will multiple each index by 0, or by 1 if it is the matching one, and return their sum, i.e. the matching index or 0
            // Note that the indexes are deliberately 1-based, to distinguished from 0 (no match)
            var index = Vector.Dot(indexes, mask);
    
            // Either return 0 for no match, or reduce the index by 1 to get the 0-based index
            return index == 0 ? -1 : index - 1;
        }
    

【问题讨论】:

  • 第一次迭代后循环无条件结束,这似乎不对
  • @harold 哎呀,我在写的时候忘记了循环!我的例子使用了一个向量,呵呵。当然,只要没有匹配,它就应该继续。
  • 借用 JsonReaderHelper.IndexOfOrLessThan 的技术:如果我们经常期望有多个向量在第一个向量中没有匹配,我们可以通过短路 if Vector&lt;ushort&gt;.Zero.Equals(mask) 来优化一些 Vector.Dot 调用。

标签: c# vectorization simd intrinsics dot-product


【解决方案1】:

正如我所见,简单的Span&lt;char&gt;.IndexOf 已经在使用 Intrinsics 来搜索简单的值。你甚至不需要转换为 char 来使用它,因为 MemoryExtensions.IndexOf 只关心大小和 Unsafe.SizeOf&lt;ushort&gt;() == sizeof(char)

同样在JsonReaderHelper.IndexOfOrLessThan 中,您会发现一个更复杂的用于搜索的矢量化示例。它使用字节搜索,但如果Span&lt;ushort&gt;.IndexOf 不合适,我相信您可以根据自己的需要进行调整。

【讨论】:

  • 这很有帮助! JsonReaderHelper.IndexOfOrLessThan 虽然是私有的,但确实提供了字节搜索版本。 (我的示例有 ushorts,但所有整数大小都可能很有趣。)
  • 所以我们可以从JsonReaderHelper.IndexOfOrLessThan 借用的方法归结为:Vector.Equals 在两个向量上。跳到下一个向量if (Vector&lt;byte&gt;.Zero.Equals(result))。否则,将向量解释为Vector&lt;ulong&gt;。对于每个ulong,如果为0,则继续。一旦一个非零,使用位旋转来提取匹配在ulong 中的位置。虽然这里所做的非矢量化工作已经优化得很好,使用点积提取索引会不会更快,正如我在原始帖子中提出的那样?还是Vector.Dot 这么慢?
  • LocateFirstFoundByte 方法只进行异或、乘法和移位。我怀疑 Vector.Dot 会比这更快
  • 实际上有两个重载。使用顶部的,它调用底部的。顶部重载首先将Vector 视为Vector&lt;ulong&gt;,并找到第一个非零ulong 传递给底部重载。问题是存在的!
【解决方案2】:

您希望编译器生成的 x86 asm 是相等比较 (pcmpeqb)、pmovmskbmovmskps(向量到具有 1 字节或 4 字节元素的位掩码),然后如果掩码非零,对第一个设置位进行位扫描(bsftzcnt)。

这将比整数点积更有效!

您已经有了比较相等,我想我已经看到了其他具有矢量->位图内在特性的 C# Q&A。如果有人想编辑这个答案或使用 C# 将自己的答案编译/JITs 到这个 asm,请这样做。我不懂 C#,我只是为了 x86 SIMD。

【讨论】:

  • Vector=>位图本身就是我非常感兴趣的东西!我真的很想看看如何在 C# 中管理它。
  • 想一想,MemoryMarshal.Cast&lt;Vector&lt;ushort&gt;, ulong&gt;() 接近了,将向量解释为Span&lt;ulong&gt;。但它不会将范围缩小到位图。
  • 您可以从 System.Runtime.Intrinsics.X86 (here) 获得 (v)movmskps,但这是 .NET Core 3 中的新功能
  • x64 的首选方法是否不同,顺便说一句?
  • @Timo:哦,对术语混乱感到抱歉。在 Microsoft/Windows 之外,“x86”包括 IA-32 和 x86-64。例如“x86 SIMD 具有高效的向量 -> 整数,与 ARM 不同,许多 ARM 微架构在此类指令上停止流水线”。我假设您实际上会使用 x86-64,而不是旧版 IA-32。但就如何向量化这一点而言,无论 32 位还是 64 位都是一样的。 SIMD 操作是相同的,这不需要超过 8 个 XMM/YMM 寄存器,因此它不会特别受益于 64 位模式。
猜你喜欢
  • 1970-01-01
  • 2021-05-19
  • 2014-06-28
  • 2018-12-29
  • 2015-05-13
  • 2020-06-01
  • 2012-11-19
  • 2023-03-24
  • 1970-01-01
相关资源
最近更新 更多