尽快将 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
中的代码控制了运行时。
我正在尝试在 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
中的代码控制了运行时。