尽快将 ND 数组向量化为一维数组

Vectorize ND-Array to 1D-Array as fast as possible

我正在尝试在 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 class 来加速计算索引(而不是像上面那样使用 modulo),但也许真的有快速的方法可以做到这一点有点像“surface memcpy”?

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

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

根据我的经验,多维数组使用起来有点麻烦,很大程度上是因为它很难访问支持数据。据我所知,没有直接的方法来复制任意类型的所有元素。

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

对于 3D/4D 阵列,您可以使用相同的模式,但似乎性能的最佳选择是独立分配切片,即 myArray[z][y * width + x],至少对于大型阵列而言。我没有使用过 4D 数组,但一般来说,如果性能是一个问题,我会避免使用多维数组。可能还有适合您需要的图书馆,但我不知道有任何具体的图书馆。

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

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 中的代码控制了运行时。