大数据集上的System.Numerics.Vector <T>

豪斯

我试图通过利用System.Numericsfloat[]数组执行SIMD操作来提高.NET Core库的性能System.Numerics现在有点时髦,我很难看清它如何带来好处。我知道,为了看到SIMD的性能提升,必须将其分摊到大量计算中,但是鉴于当前是如何实现的,因此我不知道该如何实现。

Vector<float>需要8个float值-不多也不少。如果我想对一组小于8的值执行SIMD操作,则必须将这些值复制到新数组中,并用零填充余数。如果一组值大于8,则需要复制这些值,并用零填充以确保其长度与8的倍数对齐,然后在它们上循环。长度要求是有道理的,但是对此加以妥协似乎是抵消任何性能提升的好方法。

我编写了一个测试包装器类,该类负责填充和对齐:

public readonly struct VectorWrapper<T>
  where T : unmanaged
{

  #region Data Members

  public readonly int Length;
  private readonly T[] data_;

  #endregion

  #region Constructor

  public VectorWrapper( T[] data )
  {
    Length = data.Length;

    var stepSize = Vector<T>.Count;
    var bufferedLength = data.Length - ( data.Length % stepSize ) + stepSize;

    data_ = new T[ bufferedLength ];
    data.CopyTo( data_, 0 );
  }

  #endregion

  #region Public Methods

  public T[] ToArray()
  {
    var returnData = new T[ Length ];
    data_.AsSpan( 0, Length ).CopyTo( returnData );
    return returnData;
  }

  #endregion

  #region Operators

  public static VectorWrapper<T> operator +( VectorWrapper<T> l, VectorWrapper<T> r )
  {
    var resultLength = l.Length;
    var result = new VectorWrapper<T>( new T[ l.Length ] );

    var lSpan = l.data_.AsSpan();
    var rSpan = r.data_.AsSpan();

    var stepSize = Vector<T>.Count;
    for( var i = 0; i < resultLength; i += stepSize )
    {
      var lVec = new Vector<T>( lSpan.Slice( i ) );
      var rVec = new Vector<T>( rSpan.Slice( i ) );
      Vector.Add( lVec, rVec ).CopyTo( result.data_, i );
    }

    return result;
  }

  #endregion

}

这个包装器可以解决问题。计算看起来是正确的,并且Vector<T>没有抱怨元素的输入数量。但是,它的速度是基于范围的简单for循环的两倍。

这是基准:

  public class VectorWrapperBenchmarks
  {

    #region Data Members

    private static float[] arrayA;
    private static float[] arrayB;

    private static VectorWrapper<float> vecA;
    private static VectorWrapper<float> vecB;

    #endregion

    #region Constructor

    public VectorWrapperBenchmarks()
    {
      arrayA = new float[ 1024 ];
      arrayB = new float[ 1024 ];
      for( var i = 0; i < 1024; i++ )
        arrayA[ i ] = arrayB[ i ] = i;

      vecA = new VectorWrapper<float>( arrayA );
      vecB = new VectorWrapper<float>( arrayB );
    }

    #endregion

    [Benchmark]
    public void ForLoopSum()
    {
      var aA = arrayA;
      var aB = arrayB;
      var result = new float[ 1024 ];

      for( var i = 0; i < 1024; i++ )
        result[ i ] = aA[ i ] + aB[ i ];
    }

    [Benchmark]
    public void VectorSum()
    {
      var vA = vecA;
      var vB = vecB;
      var result = vA + vB;
    }

  }

结果:

|     Method |       Mean |    Error |   StdDev |
|----------- |-----------:|---------:|---------:|
| ForLoopSum |   757.6 ns | 15.67 ns | 17.41 ns |
|  VectorSum | 1,335.7 ns | 17.25 ns | 16.13 ns |

我的处理器(i7-6700k)确实支持SIMD硬件加速,并且以64位发布模式运行,并在.NET Core 2.2(Windows 10)上启用了优化。

我意识到,这Array.CopyTo()很可能会导致性能下降,但是似乎没有简便的方法来使填充/对齐和数据集均未明确符合Vector<T>的规范。

我对SIMD相当陌生,并且我了解C#实现仍处于早期阶段。但是,我看不到有一种从中受益的明确方法,尤其是考虑到将其扩展到更大的数据集时,它是最有益的。

有没有更好的方法来解决这个问题?

冈萨雷斯

我不确定您所说的“笨拙”是什么意思,但是它现在可以完美地使用(尽管它可能表现得更好)。使用您的案例(总计浮点数),使用Haswell老年人CPU可获得10003项以下结果:

BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17134.706 (1803/April2018Update/Redstone4)
Intel Core i7-4500U CPU 1.80GHz (Haswell), 1 CPU, 4 logical and 2 physical cores
Frequency=1753753 Hz, Resolution=570.2057 ns, Timer=TSC
.NET Core SDK=2.1.602
  [Host]     : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT
  DefaultJob : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT


|   Method |      Mean |     Error |    StdDev |
|--------- |----------:|----------:|----------:|
| ScalarOp | 12.974 us | 0.2579 us | 0.2533 us |
| VectorOp |  3.956 us | 0.0570 us | 0.0505 us |
| CopyData |  1.455 us | 0.0273 us | 0.0228 us |

将数据从向量复制回数组的速度相对较慢,因为它消耗了将近一半的时间。但仍然:矢量化运算的总时间不到标量运算的三分之一...

查看反汇编(BenchmarkDotNet会生成反汇编),似乎内存复制操作使用(较慢)未对齐的操作。.Net Core的未来版本可能会对此进行研究。

您可以通过使用Span<T>MemoryMarshal.Cast将结果向量直接放到Span中来完全避免复制操作它将求和时间减少了大约。与复制相比,三分之一(以下未显示)。

作为参考,基准代码为(floatSlots = Vector<float>.Count;在基准运行之前创建数组并填充数据),并且不一定是最佳解决方案:

        [Benchmark]
        public void ScalarOp()
        {            
            for (int i = 0; i < data1.Length; i++)
            {
                sums[i] = data1[i] + data2[i];
            }            
        }

        [Benchmark]
        public void VectorOp()
        {                      
            int ceiling = data1.Length / floatSlots * floatSlots;
            int leftOver = data1.Length % floatSlots;
            for (int i = 0; i < ceiling; i += floatSlots)
            {                
                Vector<float> v1 = new Vector<float>(data1, i);                
                Vector<float> v2 = new Vector<float>(data2, i);                
                (v1 + v2).CopyTo(sums, i); 

            }
            for (int i = ceiling; i < data1.Length; i++)
            {
                sums[i] = data1[i] + data2[i];
            }
        }

        [Benchmark]
        public void CopyData()
        {                        
            Vector<float> v1 = new Vector<float>(8);
            int ceiling = data1.Length / floatSlots * floatSlots;
            int leftOver = data1.Length % floatSlots;
            for (int i = 0; i < ceiling; i += floatSlots)
            {                               
                (v1).CopyTo(sums, i);
            }
            for(int i = ceiling; i < data1.Length; i++)
            {
                sums[i] = 8;
            }                
        }

编辑:由于与矢量相同,更正了标量基准,增加了对Span和的提及MemoryMarshal.Cast

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

缺少System.Numerics.Vectors.Vector <T>

System.Numerics.Vectors'Vector <T>':基本上只是System.UInt128吗?

.NET Framework上System.Numerics.Vector <T>的初始化性能

System.Numerics.Vector.GreaterThan and bool结果

在System.Numerics.Vector <T>中使用F#度量单位

System.Numerics.Vector.ConditionalSelect用来做什么?

System.Numerics.Vector <int>仅部分初始化

C#SIMD使用System.Numerics.Vector进行排序/中位数

如何在C#中获取System.Numerics.Vector的元素?

C#中包含System.Numerics.Vector <double>的结构的指针

Protobuff序列化System.Numerics.BigInteger

包装昂贵的System.Numerics.VectorX-为什么?

使用System.Numerics.Vectors旋转2D点

C#哪里可以下载System.Numerics

VS Code Omnisharp.MsBuild.Projectmanager无法加载程序集System.Numerics.Vectors 4.1.3.0

C++:从 vector<vector<T>> 获取 T**

如何将std :: vector <short>中的数据存储在std :: vector <uint8_t>中

尝试使用protobuf-net序列化System.Numerics.Quaternion

使用System.Numerics进行矩阵乘法的完全错误的值

我如何在MonoDevelop中的Mono C#中使用System.Numerics?

为什么需要用这个简单的LINQ表达式引用System.Numerics?

什么是Org.BouncyCastle.Math.BigInteger.ToByteArrayUnsigned的.NET System.Numerics.BigInteger等效项?

将vector <T *>移动到vector <const T *>

C ++-将vector <T>转换为vector <array <T >>

C ++ vector <T> v,vector <T> * v,vector <T *> vec,哪个最快(或最高效)?

System.Numerics.dll中的BigInteger类型和FSharp.Core.dll中的BigInteger类型之间有什么区别?

为什么System.Numerics命名空间中没有Matrix3x3?C#

使用AspNetCore.SignalR.Client nuget包时找不到SignalR-System.Numerics.Vectors v4.1.4

如何从 Vector[T] 推断参数 T