我可以在树莓派4上使用.NET SIMD吗?

3

我正在编写一段代码,用于从两个数组中相应的字节进行减法运算,并计算超过给定阈值的结果字节数。据我所知,使用.NET SIMD会带来很大好处,但当我在Raspberry Pi 4上编译C#时,System.Numerics.Vector.IsHardwareAccelerated返回false。

我的dotnet版本是3.1.406,我已经添加了

  <PropertyGroup>
    <Optimize>true</Optimize>
  </PropertyGroup>

release配置添加到csproj并运行。

有没有办法在树莓派4上利用.NET的SIMD支持?也许使用.NET 5?

更新 我安装了.NET 5并尝试了.NET Intrinsics,但是都不受支持:

Console.WriteLine(System.Runtime.Intrinsics.Arm.AdvSimd.IsSupported); //false
Console.WriteLine(System.Runtime.Intrinsics.Arm.Aes.IsSupported);  //false
Console.WriteLine(System.Runtime.Intrinsics.Arm.ArmBase.IsSupported); //false
Console.WriteLine(System.Runtime.Intrinsics.Arm.Crc32.IsSupported); //false
Console.WriteLine(System.Runtime.Intrinsics.Arm.Dp.IsSupported); //false
Console.WriteLine(System.Runtime.Intrinsics.Arm.Rdm.IsSupported); //false
Console.WriteLine(System.Runtime.Intrinsics.Arm.Sha1.IsSupported); //false
Console.WriteLine(System.Runtime.Intrinsics.Arm.Sha256.IsSupported); //false

我在32位的Raspbian(Debian衍生版)上,是否有可能需要64位版本才能使其工作?

附注:为了澄清,在纯C#中,该算法的形式如下:

        public static int ScalarTest(byte[] lhs, byte[] rhs)
        {
            var result = 0;

            for (int index = 0; index < lhs.Length; index++)
            {
                var a = lhs[index];
                var b = rhs[index];
                if (b > a)
                {
                    (b, a) = (a, b);
                }
                result += ((a - b) >= 16) ? 1 : 0;
            }

            return result;
        }

"..减去相应的字节..." 两个相应字节的减法结果总是为零,对吗? - JHBonarius
1
@JHBonarius,这个我不确定最好的术语是什么,我的意思是 arr1[i] - arr2[i]。我会添加C#循环实现来解释我的意思。 - Ivan Koshelev
2个回答

6
尽管API已经完成并有文档支持,但实现还未完成。点击查看在NEON ISA中,8字节SIMD向量已经成为必不可少的部分数十年了(于2005年推出),然而.NET运行时只有在编译ARM64(2013年发布)时才会实现它们。
我不是Microsoft的员工,也不知道他们编译二进制文件的具体方式,但源代码表明,他们至少在构建ARM64目标时支持NEON。如果您想在.NET中使用这些内部函数,可以尝试64位操作系统。
有一个解决方法 - 在C++中实现性能关键的部分,为Linux编译共享库,然后使用[DllImport]从.NET中使用这些函数。我已经用这种方式构建了非平凡的Linux软件(example),使用以下gcc标志来构建DLL:-march=native -mfpu=neon-fp16 -mfp16-format=ieee -ffast-math -O3 -fPIC这样它将适用于32位操作系统,并且不需要.NET运行时的任何特殊要求,我已经在.NET Core 2.1上进行了测试。

2

在参考@Soonts的答案后,切换到64位Raspbian系统后,在NET 5中我获得了大部分我所需要的指令。

Console.WriteLine(System.Runtime.InteropServices.RuntimeInformation.OSDescription);
//Linux 5.4.51-v8+ #1333 SMP PREEMPT Mon Aug 10 16:58:35 BST 2020

Console.WriteLine(System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture);
//Arm64

Console.WriteLine(System.Environment.Is64BitOperatingSystem);           //true

Console.WriteLine(System.Numerics.Vector.IsHardwareAccelerated);        //true
Console.WriteLine(Vector<byte>.Count);                                  //16
Console.WriteLine(Vector<sbyte>.Count);                                 //16
Console.WriteLine(Vector<short>.Count);                                 //8
Console.WriteLine(Vector<ushort>.Count);                                //8
Console.WriteLine(Vector<int>.Count);                                   //4
Console.WriteLine(Vector<uint>.Count);                                  //4
Console.WriteLine(Vector<long>.Count);                                  //2
Console.WriteLine(Vector<ulong>.Count);                                 //2

Console.WriteLine(Vector<float>.Count);                                 //4
Console.WriteLine(Vector<double>.Count);                                //2

Console.WriteLine(System.Runtime.Intrinsics.Arm.AdvSimd.IsSupported);   //true
Console.WriteLine(System.Runtime.Intrinsics.Arm.Aes.IsSupported);       //false
Console.WriteLine(System.Runtime.Intrinsics.Arm.ArmBase.IsSupported);   //true
Console.WriteLine(System.Runtime.Intrinsics.Arm.Crc32.IsSupported);     //true
Console.WriteLine(System.Runtime.Intrinsics.Arm.Dp.IsSupported);        //false
Console.WriteLine(System.Runtime.Intrinsics.Arm.Rdm.IsSupported);       //false
Console.WriteLine(System.Runtime.Intrinsics.Arm.Sha1.IsSupported);      //false
Console.WriteLine(System.Runtime.Intrinsics.Arm.Sha256.IsSupported);    //false

在我的 Pi 4 上实现了一个比较两个字节数组元素绝对差异超过一定阈值的算法后,得到以下基准测试测量结果(预热后3次运行的平均值):
C# 循环:59 毫秒
System.Numerics.Vector: 21 毫秒
System.Runtime.Intrinsics.Arm.AdvSimd: 17 毫秒
从 https://gist.github.com/IKoshelev/325f0e10bee0806d7bb2c9d63d09ba9e 优化向量创建的 System.Runtime.Intrinsics.Arm.AdvSimd: 2 毫秒!!!请保留 html 标签。

我期望从NEON获得超过3倍的改进,字节很小,向量寄存器有16个。尝试将以下C++移植到.NET:https://gist.github.com/Const-me/14da47903393acd2c3fb92c0b2eb090a(未经测试,但思路相当简单) - Soonts
@Soonts,从我看来,我有几乎完全相同的代码:https://gist.github.com/IKoshelev/71a758afecb8ebc2bc152cb13f95f2a1 我猜字节数组的向量创建可能更有效,并将尝试重新解释阈值后的结果而不是应用掩码。 - Ivan Koshelev
1
@Soonts 你说得太对了,我改变了向量加载方式后,时间稳定在2毫秒!https://gist.github.com/IKoshelev/325f0e10bee0806d7bb2c9d63d09ba9e - Ivan Koshelev
你也可以尝试这个版本:https://gist.github.com/Const-me/ef93b842cd3c13c47c0b5d0ebff4a0a8。它需要 /unsafe 编译器开关,但我认为它可能会稍微快一些。 - Soonts

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接