将T[,]转换为T[][]的最快方法
本文关键字:方法 转换 | 更新日期: 2023-09-27 18:25:13
因此,并非所有数组都是相等的。多维数组可以具有非零的下限。例如,请参阅Excel PIA的Range.Value属性object[,] rectData = myRange.Value;
我需要将这些数据转换为锯齿状数组。我在下面的第一次尝试充满了复杂性。有什么优化建议吗?它需要处理下界可能不为零的一般情况。
我有这个ex方法:
public static T[][] AsJagged<T>( this T[,] rect )
{
int row1 = rect.GetLowerBound(0);
int rowN = rect.GetUpperBound(0);
int col1 = rect.GetLowerBound(1);
int colN = rect.GetUpperBound(1);
int height = rowN - row1 + 1;
int width = colN - col1 + 1;
T[][] jagged = new T[height][];
int k = 0;
int l;
for ( int i = row1; i < row1 + height; i++ )
{
l = 0;
T[] temp = new T[width];
for ( int j = col1; j < col1 + width; j++ )
temp[l++] = rect[i, j];
jagged[k++] = temp;
}
return jagged;
}
像这样使用:
public void Foo()
{
int[,] iRect1 = { { 1, 1, 1, 1 }, { 1, 1, 1, 1 }, { 1, 1, 1, 1 }, { 1, 1, 1, 1 }, { 1, 1, 1, 1 }, { 1, 1, 1, 1 }, { 1, 1, 1, 1 }, { 1, 1, 1, 1 } };
int[][] iJagged1 = iRect1.AsJagged();
int[] lengths = { 3, 5 };
int[] lowerBounds = { 7, 8 };
int[,] iRect2 = (int[,])Array.CreateInstance(typeof(int), lengths, lowerBounds);
int[][] iJagged2 = iRect2.AsJagged();
}
想知道Buffer.BlockCopy()是否有效或更快?
编辑:AsJagged需要处理引用类型。
编辑:在AsJagged()中发现错误。增加int l
;并将CCD_ 3添加到内环中。
预先查看警告/假设:
- 您似乎只使用
int
作为数据类型(或者至少可以使用Buffer.BlockCopy
,这意味着您通常可以使用原始类型) - 对于你展示的测试数据,我认为使用任何理智的方法都不会有太大的不同
话虽如此,以下实现(需要专门用于特定的基元类型(此处为int
),因为它使用fixed
)比使用内部循环的方法快大约10倍:
unsafe public static int[][] AsJagged2(int[,] rect)
{
int row1 = rect.GetLowerBound(0);
int rowN = rect.GetUpperBound(0);
int col1 = rect.GetLowerBound(1);
int colN = rect.GetUpperBound(1);
int height = rowN - row1 + 1;
int width = colN - col1 + 1;
int[][] jagged = new int[height][];
int k = 0;
for (int i = row1; i < row1 + height; i++)
{
int[] temp = new int[width];
fixed (int *dest = temp, src = &rect[i, col1])
{
MoveMemory(dest, src, rowN * sizeof(int));
}
jagged[k++] = temp;
}
return jagged;
}
[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
unsafe internal static extern void MoveMemory(void* dest, void* src, int length);
使用以下"测试代码":
static void Main(string[] args)
{
Random rand = new Random();
int[,] data = new int[100,1000];
for (int i = 0; i < data.GetLength(0); i++)
{
for (int j = 0; j < data.GetLength(1); j++)
{
data[i, j] = rand.Next(0, 1000);
}
}
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
int[][] dataJagged = AsJagged(data);
}
Console.WriteLine("AsJagged: " + sw.Elapsed);
sw = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
int[][] dataJagged2 = AsJagged2(data);
}
Console.WriteLine("AsJagged2: " + sw.Elapsed);
}
其中AsJagged
(第一种情况)是您的原始函数,我得到以下输出:
AsJagged: 00:00:00.9504376
AsJagged2: 00:00:00.0860492
因此,确实有一种更快的方法可以做到这一点,然而,根据测试数据的大小、实际执行此操作的次数以及您是否愿意允许不安全和p/Invoke代码,您可能不需要它。
话虽如此,我们使用的是double
的大矩阵(比如7000x1000个元素),它确实产生了巨大的差异。
更新:关于使用Buffer.BlockCopy
我可能会忽略一些Marshal
或其他技巧,但我认为在这里使用Buffer.BlockCopy
是不可能的。这是因为它要求源和目标数组都是Array
。
在我们的示例中,目的地是一个数组(例如int[] temp = ...
),但源不是。虽然我们"知道"对于基元类型的二维数组,布局是这样的,每个"行"(即第一维)都是内存中该类型的数组,但如果不首先复制该数组,就没有安全的方法(如unsafe
)来获得该数组。因此,我们基本上需要使用一个只处理内存而不关心内存实际内容的函数,比如MoveMemory
。顺便说一句,Buffer.BlockCopy
的内部实现也做了类似的事情。
您的复杂性是O(N*M)N-行数,M-列数。这是在复制N*M值时所能得到的最好结果。。。
Buffer.BlockCopy可能比您的内部for循环更快,但如果编译器知道如何正确处理这些代码,并且您不会获得任何进一步的速度,我也不会感到惊讶。你应该测试一下以确定。
您可以通过完全不复制数据来实现更好的性能(以稍微慢一点的查找为代价)。如果您创建了一个"array-row"类,该类包含rect和行号,并提供了一个访问正确列的索引器,那么您可以创建这样的行的数组,并完全省去复制操作。
创建这样一个"数组行"数组的复杂性是O(N)。
编辑:一个ArrayRow类,只是因为它让我bug…
ArrayRow可能看起来像这样:
class ArrayRow<T>
{
private T[,] _source;
private int _row;
public ArrayRow(T[,] rect, int row)
{
_source = rect;
_row = row;
}
public T this[int col] { get { return _source[_row, col]; } }
}
现在您创建了一个ArrayRows数组,根本不复制任何内容,优化器很有可能优化按顺序访问整行。