Самый быстрый способ конвертировать T [,] в T [] []? - PullRequest
8 голосов
/ 20 февраля 2012

Так получается все массивы не созданы равными.Многомерные массивы могут иметь ненулевые нижние границы.См., Например, свойство Range.Value в Excel PIA 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;и добавил col1 + width во внутренний цикл.

Ответы [ 2 ]

7 голосов
/ 20 февраля 2012

Вид предостережения / предположения впереди:

  • Похоже, вы используете только 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 (скажем, 7000x10000 элементов), где это действительно имело огромное значение.

Обновление: об использовании Buffer.BlockCopy

Я мог бы пропустить Marshal или другой трюк, но я не думаю, что здесь можно использовать Buffer.BlockCopy. Это связано с тем, что для него требуется, чтобы массив источника и назначения был, ну, Array.

В нашем примере местом назначения является массив (например, int[] temp = ...), а источником - нет. Хотя мы «знаем», что для двумерных массивов примитивных типов макет таков, что каждая «строка» (т. Е. Первое измерение) является массивом типа в памяти, не существует безопасного (как в unsafe) способа получить этот массив без затрат на копирование в первую очередь. Таким образом, нам в основном нужно использовать функцию, которая просто работает с памятью и не заботится о фактическом ее содержании - например, MoveMemory. Кстати, внутренняя реализация Buffer.BlockCopy делает нечто подобное.

5 голосов
/ 20 февраля 2012

Ваша сложность O (N * M) N - количество строк, M - количество столбцов.Это лучшее, что вы можете получить при копировании значений N * M ...

Buffer.BlockCopy может быть быстрее, чем ваш внутренний цикл for, но я не удивлюсь, если компилятор знает, как правильно обрабатывать этот коди ты не получишь дальнейшую скорость.Вы должны проверить это, чтобы убедиться.

Возможно, вам удастся добиться лучшей производительности , вообще не копируя данные (за счет возможного более медленного поиска).Если вы создаете класс 'array row', который содержит ваш прямоугольник и номер строки и предоставляет индексатор, который обращается к правильному столбцу, вы можете создать массив таких строк и полностью сохранить свое копирование.

Сложность создания такого массива «строк массива» - O (N).

РЕДАКТИРОВАТЬ: класс ArrayRow, просто потому, что он мне мешает ...

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, вы вообще ничего не копируете, и у оптимизатора есть хороший шанс оптимизировать доступ ко всей строке в последовательности.

...