尽可能快地将 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的主要内容,如果未能解决你的问题,请参考以下文章