【问题标题】:Intrinsics SIMD instruction to replace values用于替换值的内部 SIMD 指令
【发布时间】:2020-11-22 03:41:52
【问题描述】:

我想知道如何替换 Vector128<byte> 中的字节值

我认为可以假设下面的代码中我们有一个resultvector 这些价值观:

在这里我想创建一个新向量,其中所有“0”都将替换为“2” 并且所有“1”都将被替换为“0”,如下所示:

我不确定这是否有内在函数或如何实现?

谢谢!

        //Create array
        byte[] array = new byte[16];
        for (int i = 0; i < 4; i++) { array[i] = 0; }
        for (int i = 4; i < 8; i++) { array[i] = 1; }
        for (int i = 8; i < 16; i++) { array[i] = 0; }


        fixed (byte* ptr = array)
        {
            byte* pointarray = &*((byte*)(ptr + 0)); 
            System.Runtime.Intrinsics.Vector128<byte> resultvector = System.Runtime.Intrinsics.X86.Avx.LoadVector128(&pointarray[0]);

            //<0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0>
            //resultvector
        }

【问题讨论】:

  • 也许像2-(resultvector&lt;&lt;1) 之类的东西(如果无法转换,只需添加到自身)。但是您实际上是如何计算 resultvector 的? (如果您能够修改该计算,可能会有更有效的方法)。
  • 我编辑了我的帖子。我认为这个例子等于真实的例子是我留下这个resultvector的地方。我不确定这意味着什么:2-(resultvector&lt;&lt;1)?我使用 C#。
  • &lt;&lt; 是移位运算符。我对 C# 不是很熟悉,所以我不知道它是否允许 SIMD 类型(或者 C# 是否为 SIMD 类型重载运算符......)。本质上,您需要为每个元素计算2-(a[i]+a[i])(必须有一种方法,只需两条 SIMD 指令)。
  • 你可能不明白我所说的“你如何实际计算resultvector”是什么意思。如果您的代码您实际计算它的方式,只需将array[i]=0; 替换为array[i]=2; 并将array[i]=1; 替换为array[i]=0(我确定这不是您需要的......)。
  • 是的,我不确定&lt;&lt; 是否可以使用。我从未见过在 C# 中使用的那个。我认为这是不可能的。我相信 C# 中一定有某种 SIMD 指令可以以某种方式使用,我希望。

标签: c# simd intrinsics


【解决方案1】:

该指令是 pshufb,在现代 .NET 中以 Avx2.ShuffleSsse3.Shuffle 的形式提供 16 字节版本。两者都非常快,在现代 CPU 上只有 1 个周期的延迟。

将您的源数据传递给 shuffle control mask 参数,并为第一个参数设置一个特殊值,即被打乱的字节,如下所示:

// Create AVX vector with all zeros except the first byte in each 16-byte lane which is 2
static Vector256<byte> makeShufflingVector()
{
    Vector128<byte> res = Vector128<byte>.Zero;
    res = Sse2.Insert( res.AsInt16(), 2, 0 ).AsByte();
    return Vector256.Create( res, res );
}

有关详细信息,请参阅 this article 第 18 页上的 _mm_shuffle_epi8 部分。

更新:如果您没有 SSSE3,您可以在 SSE2 中执行相同操作,只需 2 条指令而不是 1 条:

static Vector128<byte> replaceZeros( Vector128<byte> src )
{
    src = Sse2.CompareEqual( src, Vector128<byte>.Zero );
    return Sse2.And( src, Vector128.Create( (byte)2 ) );
}

顺便说一句,.NET 中有一个 performance problem 可以防止编译器在循环之外加载常量。如果您要在循环中调用该方法并希望最大限度地提高性能,请考虑将两个常量向量(0 和 2)作为方法参数传递。

【讨论】:

  • 谢谢!我有一个小问题要完全遵循这一点。我不能在我的 CPU 上使用Vector256,但我确实有这样的shuffle 方法。但是不确定我应该在第二个参数中添加什么 mask 吗? System.Runtime.Intrinsics.X86.Avx.Shuffle(resultvector, Vector128&lt;byte&gt; mask)
  • @Andreas 如果您没有 AVX2,请使用 16 字节版本,Ssse3.Shuffle。第二个参数中的掩码是您的源向量,字节值为 0 或 1。代码将对您的每个输入字节执行此操作:( 0 == b ) ? 2 : 0,因为 shuffle 源参数除了第一个字节之外全为零是 2。
  • 如果你有 AVX2 就不能使用 Vector256.Create 方法,用其他方式创建相同的值。您可以从 32 字节长的字节数组中加载,或者更好地使用 stackalloc 来避免 GC。
  • Ssse3.ShuffleAvx.Shuffle 在尝试运行它们时不受我的平台支持。支持Sse2.Shuffle(resultvector, Convert.ToByte(2)),但第一个参数只能采用例如int 而不是byte。所以看来我一次只能运行 4 条指令? (虽然我还没有弄清楚如何使用它)
  • @Andreas 那是 2010 年的,你运气不好。然后,您将不得不做其他事情,例如使用Sse2.CompareEqual 指令将字节与零进行比较,然后使用Sse2.And 来获得您需要的值。顺便说一句,根据 cpubenchmark.net 的说法,一款售价 60 美元的现代 CPU Ryzen 3 1200 比 Opteron 6174 快两倍,尽管它只有 4 个内核而不是 12 个。
猜你喜欢
  • 2017-08-19
  • 1970-01-01
  • 1970-01-01
  • 2019-06-18
  • 1970-01-01
  • 2012-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多