Самый эффективный способ получить столбцы многомерного массива в C - PullRequest
1 голос
/ 14 июля 2010

Я пытаюсь создать матричную структуру данных в C. У меня есть структура, и у меня есть двумерный массив пустых указателей (размер динамически определяется в куче) для части груза (данных) в этой структуре.

Учитывая индекс столбца, я хочу получить значения этого столбца в одномерном массиве.Это легко сделать с помощью цикла for или while.Но если число строк в этой матрице равно N, тогда для получения вектора столбца потребуется O (N) времени.Могу ли я сделать это более эффективно с помощью операций с памятью, таких как memcpy и как?В противном случае, как я могу улучшить производительность (мои данные довольно структурированы, и мне нужно хранить их в какой-то матрице).

Ответы [ 4 ]

4 голосов
/ 14 июля 2010

Если количество строк в столбце равно N, вы не можете копировать, читать или иным образом манипулировать всем столбцом за меньшее время, чем O (N). Это твердая нижняя граница; каждый элемент должен быть рассмотрен, и их N.

Так что нет, вы не можете сделать это быстрее, чем O (N).

Обратите внимание, что выражение x[3][5] переводится компилятором в x+((3*num_cols)+5)*size_of_element для двумерных массивов известного размера. Таким образом, один из способов сделать ваш массив быстрее - это удалить его динамический размер.

Другим важным моментом является то, что последовательный доступ к памяти не всегда самый быстрый - поэтому простое вращение массива на девяносто градусов не обязательно даст вам лучшие результаты. Посмотрите на блокирование как метод оптимизации. Итог: какая структура памяти лучше всего зависит от ваших шаблонов доступа и аппаратных параметров, таких как длина строки кэша и размер кэша.

1 голос
/ 14 июля 2010

Если вы хотите скопировать данные в матрице, вы не сможете сделать это менее чем за O (N), будь то строка или столбец, за исключением малого N, где могут помочь аппаратные функции.

Однако, если ваши матрицы неизменны, вы можете использовать дым и зеркала, чтобы создать иллюзию наличия отдельного вектора-столбца.

Код ниже введен прямо в текстовое поле ответа и даже не был скомпилирован. Используйте на свой страх и риск!

Ваш тип матрицы определяется как структура, таким образом:

typedef struct 
{
    unsigned int refCount;  // how many Matrixes are referencing this data ref
    size_t lineWidth;       // number of doubles between element at row = n, col = 0 and row = n +1, col = 0 
    double* data;           // the actual data
} DataRef;

typedef struct
{
    size_t rows;            // num rows in matrix
    size_t cols;            // num cols in matrix
    size_t dataOffset;      // offset in doubles from the start of data of element at row = 0, col = 0
    DataRef* data;
} Matrix;

Чтобы создать новую матрицу (я упустил всю обработку ошибок, чтобы сделать ее проще).

Matrix* matrix_create(size_t rows, size_t cols, const double* values)
{
    Matrix* ret = calloc(1, sizeof *ret);
    ret->rows = rows;
    ret->cols = cols;
    ret->dataOffset = 0;
    ret->data = calloc(1, sizeof *dataRef);
    ret->data->lineWidth = cols;
    ret->data->data = allocateAndCopy(rows * cols, values); // mallocs a new block of doubles big enough for the values
    ret->data->refCount = 1;
    return ret;
}

Чтобы получить доступ к элементу (опять же, нет обработки ошибок, например, ошибки границ)

double matrix_elementAt(Matrix* matrix, size_t row, size_t col)
{
    size_t offset = matrix->dataOffset + row * matrix->data->lineWidth + col;
    return *(matrix->data->data + offset);
}

Чтобы создать новую матрицу из прямоугольной области другой матрицы (опять же, требуется обработка ошибок)

Matrix* matrix_createFromRegion(Matrix* old, size_t startRow, size_t startCol, size_t rows, size_t cols)
{
    Matrix* ret = calloc(1, sizeof *ret);
    ret->rows = rows;
    ret->cols = cols;
    ret->dataOffset = old->dataOffset + startRow * old->dataLineWidth + startCol;
    ret->data = old->data;
    ret->data->refCount++;
    return ret;
}

Чтобы создать новую матрицу из столбца в другой матрице:

Matrix* vector = matrix_createFromRegion(aMatrix, 0, colYouWant, matrix_numRows(aMatrix), 1);

освободить матрицу

void matrix_free(Matrix* aMatrix)
{
    if (aMatrix->data->refCount == 1)
    {
        free(aMatrix->data->data);
        free(aMatrix->data);
    }
    else
    {
        aMatrix->data->refCount--;
    }
    free(aMatrix);
}

Если вам нужны изменяемые матрицы, каждый раз, когда вы изменяете элемент, проверьте refCount и, если он больше 1, скопируйте DataRef перед его изменением (уменьшите refCount для старого dataRef), в противном случае измените dataRef на месте.

Теперь вышеприведенное использует множество malloc и поэтому может быть менее эффективным, чем наивная реализация для маленьких матриц. Однако вы можете сохранить список неиспользуемых структур DataRef и структур Matrix, и вместо того, чтобы освобождать их, когда вы закончите, поместите их в свободный список. При размещении новых, получите структуры из свободных списков, если они не пусты. Таким образом, получение матрицы, представляющей столбец существующей матрицы, часто занимает постоянное время.

1 голос
/ 14 июля 2010

Мое решение:

  1. Не используйте многомерные массивы. Они негибкие до C99 (не могут изменять все размеры) и не позволяют выполнять эффективные операции, подобные следующим. Вместо этого просто используйте одномерный массив и выполните арифметику индексации элемента самостоятельно.

  2. Теперь вы можете установить указатель src, указывающий на первый элемент столбца (src = &matrix[row*ncols+col];), и скопировать столбец с помощью: for (i=0; i<nrows; i++, src+=ncols) dest[i] = *src;

1 голос
/ 14 июля 2010

Как говорит Бореалид, вы не можете улучшить O (N). Однако вы можете ускорить операцию копирования, если вы переупорядочите свои данные так, чтобы строки были столбцами, а столбцы - строками. Это позволит вам использовать memcpy для дублирования данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...