尽可能快地将 ND-Array 向量化为 1D-Array

Posted

技术标签:

【中文标题】尽可能快地将 ND-Array 向量化为 1D-Array【英文标题】:Vectorize ND-Array to 1D-Array as fast as possible 【发布时间】:2021-12-28 12:21:40 【问题描述】:

我正在尝试在 C# 中将一个 n 维数组向量化为一维数组,以便以后使用线性索引(无论元素的类型如何)来简化工作。

到目前为止,我一直在使用 Buffer.BlockCopy 来做到这一点(只要元素的数量没有改变,甚至可以从 n 维重塑为 m 维)但不幸的是,我遇到了不得不重塑元素不是的数组原始类型(double、single、int),在这种情况下 Buffer.BlockCopy 不起作用(string 的示例数组或任何其他非原始类型)。

目前我的解决方案是为非原始类型制作特殊情况:

/// <summary>Vectorize ND-array</summary>
/// <param name="arrayNd">ND-Array to vectorize.</param>
/// <returns>Surface copy as 1D array.</returns>
public static Array Vectorize(Array arrayNd)

    // Check arguments
    if (arrayNd == null)  return null; 
    var elementCount = arrayNd.Length;

    // Create 1D array
    var tarray = arrayNd.GetType();
    var telem = tarray.GetElementType();
    var array1D = Array.CreateInstance(telem, elementCount);

    // Surface copy
    if (telem.IsPrimitive)
    
        // Block copy only works for array whose elements are primitive types (double, single, ...)
        var numberOfBytes = Buffer.ByteLength(arrayNd);
        Buffer.BlockCopy(arrayNd, 0, array1D, 0, numberOfBytes);
    
    else
    
        // Slow version for other element types
        // NB: arrayNd.GetValue(...) does not support linear indexing so need to compute indices for each dimension (very slow !!)
        var indices = new int[arrayNd.Rank];
        for (var i = 0; i < elementCount; i++)
        
            var idx = i;
            for (var d = arrayNd.Rank - 1; d >= 0; d--)
            
                var l = arrayNd.GetLength(d);
                indices[d] = idx % l;
                idx /= l;
            

            array1D.SetValue(arrayNd.GetValue(indices), i);
        
    

    // Return as 1D
    return array1D;

所以这现在适用于所有类型:

var double1D = Vectorize(new double[3, 2, 5]); // Fast BlockCopy
var string1D = Vectorize(new string[3, 2, 5]); // Slow solution

我已经有一个自己的 NEnumerator 类来加速计算索引(而不是像上面那样使用 modulo),但也许真的有快速的方法来制作这种“表面 memcpy”?

NB1:我想避免使用unsafe 代码,但如果这是唯一的方法...

NB2:我真的很想和System.Array 一起工作(最终我会做一堆T[] Vectorize(T[,,,,] array) 重载,但这不是问题)

【问题讨论】:

也许你能想出一些办法:docs.microsoft.com/en-us/dotnet/standard/memory-and-spans, ***.com/questions/52750582/… 你真的需要这个用于任意尺寸吗?我很少看到任何超过 3 维的数组。请注意,数组 value 可能是 Vector3 或某些自定义类型。 甚至我在野外看到的 3d 数组出于性能和内存处理的原因也一直在使用锯齿状数组,即 T[][,] 而不是 T[,,] @JonasH 我使用的数据通常是 2D 到 4D(几乎没有 5D),我编写了一个包含 Reshape、Sort、Permute、Mean、Max 等的库。函数以非常通用的方式(所以很容易专门化并强制使用T[]T[,],......直到必要的类型安全......这个想法不是非常快(我自己的NEnumerator很好)。 .. 在这里,我想知道对于非基元是否存在快速且可立即使用的BlockCopy 等效项,因为听起来很明显数据在内存中是一维的,因此应该更快地进行类似缓冲区的复制。 我也做过同样的事情,但是对于非原始类型,我使用Array.Copy() 进行浅拷贝,因为不支持Buffer.BlockCopy()。还有Marshal.Copy()函数有待探索。 【参考方案1】:

根据我的经验,使用多维数组有点痛苦,这在很大程度上是因为访问支持数据非常困难。据我所知,没有直接的方法可以复制任意类型的所有元素。

因此,我倾向于为我的 2D 类型使用自定义类型,该类型使用线性数组作为后备存储,并使用像 myArray[y * width + x] 这样的索引。有了这个模型,整个练习就变成了无操作,你可以得到一个指针来传递给本机代码,它在序列化等方面效果更好。

对于 3D/4D 数组,您可以使用相同的模式,但似乎最好的性能选择是独立分配切片,即myArray[z][y * width + x],至少对于大型数组。我没有使用 4D 数组,但一般来说,如果性能是一个问题,我会避免使用多维数组。那里可能还有可能满足您需求的库,但我不知道有任何特定的库。

但是,查看您的代码,我预计会有一些可能的改进。您目前正在对 GetLength 进行 N 次调用,each 元素的模数和除法。所以我希望这样的事情会更快一点:

public static Array MultidimensionalToLinear(Array arr)

    var rank = arr.Rank;
    var lengths = new int[rank];

    for (int i = 0; i < rank; i++)
    
        lengths[i] = arr.GetLength(i);
    

    var linearLength = arr.Length;
    var result = Array.CreateInstance(arr.GetType().GetElementType(), linearLength);
    var index = new int[rank];
    var linearIndex = 0;
    CopyRecursive(0, index, result, ref linearIndex);

    void CopyRecursive(int rank, int[] index, Array result, ref int linearIndex)
    
        var lastIndex = index.Length - 1;
        if (rank == lastIndex)
        
            for (int i = 0; i < lengths[lastIndex]; i++)
            
                index[lastIndex] = i;
                result.SetValue(arr.GetValue(index), linearIndex);
                linearIndex++;
            
        
        else
        
            for (int i = 0; i < lengths[rank]; i++)
            
                index[rank] = i;
                CopyRecursive(rank +1, index, result, ref linearIndex);
            
        
    
    return result;

但是,在测量时,性能改进似乎相当小。可能是由于GetValue 中的代码主导了运行时。

【讨论】:

以上是关于尽可能快地将 ND-Array 向量化为 1D-Array的主要内容,如果未能解决你的问题,请参考以下文章

如何尽可能快地将像素与其周围的像素进行比较?

将 C++ 类优化为与 C-struct 一样快

HAProxy介绍

使用 System.Text.Json 有条件地将对象序列化为单个字符串

检查 ND-Array 列中的 Nan 值并删除它们

计算混合实复矩阵向量积的最快方法是啥?