【问题标题】:Span and two dimensional Arrays跨度和二维数组
【发布时间】:2019-03-15 23:26:16
【问题描述】:

是否可以将新的System.Memory Span struct 与二维数据数组一起使用?

double[,] testMulti = 
    {
        { 1, 2, 3, 4 },
        { 5, 6, 7, 8 },
        { 9, 9.5f, 10, 11 },
        { 12, 13, 14.3f, 15 }
    };

double[] testArray = { 1, 2, 3, 4 };
string testString = "Hellow world";

testMulti.AsSpan(); // Compile error
testArray.AsSpan();
testString.AsSpan();

虽然 testArray 和 testString 有一个 AsSpan 扩展,但 testMulti 不存在这样的扩展。

Span 的设计是否仅限于处理一维数据数组?
我还没有找到使用 Span 处理 testMulti 数组的明显方法。

【问题讨论】:

    标签: c# arrays .net multidimensional-array system.memory


    【解决方案1】:

    正如 John Wu 已经提到的,跨度是一维的。 您当然可以自己实现 2D span,但 Microsoft 已经为我们做到了。

    查看文档here
    您可以找到寻址 nuget 包here
    该软件包还提供了一个 Memory2D。

     var arr = new int[,] { {1,2,3},{2,2,3},{3,2,3} };
     var spn = arr.AsSapn2D();
     // Now use it similar to a normal span
     // The access is of course a bit different since we are using a 2D data structure.
     Console.WriteLine(spn[0..2,..]);
    

    【讨论】:

      【解决方案2】:

      您可以使用非托管内存创建Span。这将允许您不加选择地切片和切块

      unsafe
      {
          Span<T> something = new Span<T>(pointerToarray, someLength); 
      }
      

      完整演示

      unsafe public static void Main(string[] args)
      {
         double[,] doubles =  {
               { 1, 2, 3, 4 },
               { 5, 6, 7, 8 },
               { 9, 9.5f, 10, 11 },
               { 12, 13, 14.3f, 15 }
            };
      
         var length = doubles.GetLength(0) * doubles.GetLength(1);
      
         fixed (double* p = doubles)
         {
            var span = new Span<double>(p, length);
            var slice = span.Slice(6, 5);
      
            foreach (var item in slice)
               Console.WriteLine(item);
         }
      }
      

      输出

      7
      8
      9
      9.5
      10
      

      其他选择是重新分配给一个单维数组,接受惩罚并且不要Pass-Go

      • BlockCopy
      • 或直接 p/invoke memcpy 并使用 unsafe 和指针
      • Cast&lt;T&gt; 例如multiDimensionalArrayData.Cast&lt;byte&gt;().ToArray()

      前 2 个对于大型数组来说性能更高。

      【讨论】:

      • 我认为你代码中的长度不正确,应该是 var length = testMulti.GetLength(0) * testMulti.GetLength(1);
      • 由于没有更好的答案,我将其标记为答案,显然不涉及不安全代码的解决方案会更可取。将结构转换为锯齿状数组似乎是解决我认为 System.Memory 库设计中的空白的最佳方法。
      • @Mick 是的,确实没有合适的答案,它的实现和设计之一,内部跨度是不安全的,并且指针做它的事情,在你的情况下使用锯齿状数组是合适的,但如果需要则不需要切穿维度。尽管即使使用指针的一揽子解决方案也不能很好地工作,并且固定只能用于某些类型。所有这一切都不是该功能的限制,或者它只是尚未构建,而是 CLR 的限制而没有重新分配。因此,您可以通过似乎填补空白的指针创建跨度
      • 我认为您的长度计算过大。文档说 T 的 Span 构造函数采用 T 元素的数量,而不是字节大小。您可以删除* sizeof(double)
      【解决方案3】:

      也许使用锯齿状数组而不是多维数组会更成功。

      double[][] testMulti = 
          {
              new double[] { 1, 2, 3, 4 },
              new double[] { 5, 6, 7, 8 },
              new double[] { 9, 9.5f, 10, 11 },
              new double[] { 12, 13, 14.3f, 15 }
          };
      
      Span<double[]> span = testMulti.AsSpan(2, 1);
      Span<double> slice = span[0].AsSpan(1, 2);
      
      foreach (double d in slice)
          Console.WriteLine(d);
      
      slice[0] = 10.5f;
      
      Console.Write(string.Join(", ", testMulti[2]));
      
      Console.ReadLine();
      

      输出

      9.5
      10
      9, 10.5, 10, 11
      

      【讨论】:

      • 严格来说不是我的问题的答案,因为 testMulti 类型不同。
      • 这似乎有点奇怪,这适用于锯齿状数组,但对于多维数组没有简单的解决方案
      【解决方案4】:

      作为@saruman,我不相信这是可能的。

      您需要首先使用Fast way to convert a two dimensional array to a List ( one dimensional )Convert 2 dimensional array 中所示的技术获取一个新的一维数组。

      【讨论】:

        【解决方案5】:

        所有跨度都是一维的,因为内存是一维的。

        您当然可以将各种结构映射到一维内存上,但 Span 类不会为您做这件事。但是你可以很容易地自己写一些东西,例如:

        public class Span2D<T> where T : struct
        {
            protected readonly Span<T> _span;
            protected readonly int _width;
            protected readonly int _height;
        
            public Span2D(int height, int width)
            {
                T[] array = new T[_height * _width];
                _span = array.AsSpan();
            }
        
            public T this[int row, int column]
            {
                get
                {
                    return _span[row * _height + column];
                }
                set
                {
                    _span[row * _height + column] = value;
                }
            }
        }
        

        棘手的部分是实现Slice(),因为二维结构的语义有点模棱两可。您可能只能按一个维度对这种结构进行切片,因为按另一个维度切片会导致内存不连续。

        【讨论】:

        • 这也是个好主意
        • 您只能在另一个ref struct 上容纳ref struct 类型的字段。 Span&lt;T&gt;ref struct,而在您的代码中,Span2D&lt;T&gt; 是一个类。
        • @EduardDumitru 是正确的 - 此代码不会按原样编译。 Span2D 需要是 ref struct 而不是类,或者保存数据的类成员需要是 Memory 而不是 Span。
        猜你喜欢
        • 2013-03-27
        • 1970-01-01
        • 1970-01-01
        • 2016-07-18
        • 1970-01-01
        • 2016-08-07
        相关资源
        最近更新 更多