C Матричное умножение Динамически размещаемые матрицы

Я работаю в рамках определенного ограничения выделения матричной памяти, которое создает матрицы как таковые:

float * matrix_data = (float *) malloc(rows * cols * sizeof(float));

Я храню эту матрицу внутри массива таких структур:

#define MAX_MATRICES 100

struct matrix{
    char matrixName[50];
    int rows;
    int columns;
    float* matrix_data;
};
typedef struct matrix matrix_t;

matrix_t our_matrix[MAX_MATRICES];

Учитывая, что это так, и я не создаю матрицы с помощью двумерного массива, такого как MATRIX[SIZE][SIZE]: как правильно умножить две матрицы, созданные таким образом?

С этой текущей реализацией, если я хочу сделать что-то вроде вычитания, я делаю это следующим образом:

int max_col = our_matrix[matrix_index1].columns;
      free(our_matrix[number_of_matrices].matrix_data);
      our_matrix[number_of_matrices].data = (float *) malloc(our_matrix[matrix_index1].rows * our_matrix[matrix_index1].columns * sizeof(float)); 
      float *data1 = our_matrix[matrix_index1].matrix_data;
      float *data2 = our_matrix[matrix_index2].matrix_data;

      int col, row;
      for(col = 1; col <= our_matrix[matrix_index2].columns; col++){
        for(row = 1; row <= our_matrix[matrix_index2].rows; row++){
          our_matrix[number_of_matrices].matrix_data[(col-1) + (row-1) * max_col] =
            (data1[(col-1) + (row-1) * (max_col)]) - (data2[(col-1) + (row-1) * (max_col)]);  
        }
      }

Это достаточно просто, потому что размеры matrix_index1 и matrix_index2 идентичны, и матрица, которую они возвращают, также имеет одинаковые размеры.

Как я могу добиться умножения матриц с помощью этого метода построения матриц?

Вы вообще не используете i в расчетах. Наверное поэтому не работает. В противном случае кажется просто отлично.

Sami Kuhmonen 28.05.2019 08:05

Я действительно беспокоился, что моя неправильная реализация может повлиять на интерпретацию вопроса на что-то вроде «почему мое решение не работает», когда на самом деле я спрашиваю: «что работает?» Я удалю свою попытку и спрошу снова.

Davide Lorino 28.05.2019 08:15

Пожалуйста, покажите our_matrix декларацию. Это не float*.

KamilCuk 28.05.2019 08:18

@KamilCuk извините, я переименовал и добавил, чтобы было понятнее

Davide Lorino 28.05.2019 08:37

Вы делаете свой код излишне сложным, используя плоский массив поплавков. Вы должны определить 2D-массив, чтобы упростить кодирование и понимание

Rishikesh Raje 28.05.2019 08:47

Полностью открыт для этого предложения, я просто понятия не имею, как это сделать.

Davide Lorino 28.05.2019 08:49

Чтобы добиться матричного умножения, вам просто нужно больше циклов, но способ реализации матрицы в порядке. Кроме того, обратите внимание, что вы можете сделать свой код более читабельным, используя переменные для количества столбцов и количества строк вместо того, чтобы всегда обращаться к атрибуту в массиве структуры. Вы также можете зациклить от 0 до n-1 вместо 1 до n и удалить круглые скобки там, где они не нужны.

Joël Hecht 28.05.2019 09:41
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
7
729
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Напишите правильную абстракцию, а затем работайте над собой. Будет намного проще:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

struct matrix_s {
    char matrixName[50];
    size_t columns;
    size_t rows;
    float* data;
};

typedef struct matrix_s matrix_t;

void m_init(matrix_t *t, size_t columns, size_t rows) {
    t->rows = rows;
    t->columns = columns;
    t->data = calloc(rows * columns, sizeof(*t->data));
    if (t->data == NULL) abort();
}

size_t m_columns(const matrix_t *t) {
    return t->columns;
}

size_t m_rows(const matrix_t *t) {
    return t->rows;
}

// matrix_get 
// (x,y) = (col,row) always in that order
float *m_get(const matrix_t *t, size_t x, size_t y) {
    assert(x < m_columns(t));
    assert(y < m_rows(t));
    // __UNCONST
    // see for example `char *strstr(const char *haystack, ...` 
    // it takes `const char*` but returns `char*` nonetheless.
    return (float*)&t->data[t->rows * x + y];
}

// fill matrix with a fancy patterns just so it's semi-unique
void m_init_seq(matrix_t *t, size_t columns, size_t rows) {
    m_init(t, columns, rows);
    for (size_t i = 0; i < t->columns; ++i) {
        for (size_t j = 0; j < t->rows; ++j) {
            *m_get(t, i, j) = i + 100 * j;
        }
    }
}

void m_print(const matrix_t *t) {
    printf("matrix %p\n", (void*)t->data);
    for (size_t i = 0; i < t->columns; ++i) {
        for (size_t j = 0; j < t->rows; ++j) {
            printf("%5g\t", *m_get(t, i, j));
        }
        printf("\n");
    }
    printf("\n");
}

void m_multiply(matrix_t *out, const matrix_t *a, const matrix_t *b) {
    assert(m_columns(b) == m_rows(a));
    assert(m_columns(out) == m_columns(a));
    assert(m_rows(out) == m_rows(b));
    // Index from 0, not from 1
    // don't do `(col-1) + (row-1)` strange things
    for (size_t col = 0; col < m_columns(out); ++col) {
        for (size_t row = 0; row < m_rows(out); ++row) {
            float sum = 0;
            for (size_t i = 0; i < m_rows(a); ++i) {
                sum += *m_get(a, col, i) * *m_get(b, i, row);
            }
            *m_get(out, col, row) = sum;
        }
    }
}

int main()
{
    matrix_t our_matrix[100];

    m_init_seq(&our_matrix[0], 4, 2);
    m_init_seq(&our_matrix[1], 2, 3);

    m_print(&our_matrix[0]);
    m_print(&our_matrix[1]);

    m_init(&our_matrix[2], 4, 3);
    m_multiply(&our_matrix[2], &our_matrix[0], &our_matrix[1]);

    m_print(&our_matrix[2]);

    return 0;
}

Протестировано на онлайнgdb, пример вывода:

matrix 0xf9d010
    0     100   
    1     101   
    2     102   
    3     103   

matrix 0xf9d040
    0     100     200   
    1     101     201   

matrix 0xf9d060
  100   10100   20100   
  101   10301   20501   
  102   10502   20902   
  103   10703   21303   

Без абстракции это просто большой беспорядок. Это было бы что-то вроде:

  int col, row;
  for(col = 0; col < our_matrix[number_of_matrices].columns; col++){
    for(row = 0; row < our_matrix[number_of_matrices].rows; row++){
        for (size_t i = 0; i < our_matrix[matrix_index1].rows; ++i) {
            our_matrix[number_of_matrices].data[col * our_matrix[number_of_matrices].columns + row] = 
                our_matrix[matrix_index1].data[col * our_matrix[matrix_index1].columns + i] +
                our_matrix[matrix_index2].data[i * our_matrix[matrix_index2].columns + row];
        }  
    }
  }

Примечания:

  • Итерация от 0 до < читается намного легче, чем все (col-1) * ... + (row-1).
  • Не забудьте проверить, соответствуют ли индексы нашим границам. Это легко сделать даже с помощью простого утверждения, например. assert(row < matrix->rows && col < matrix->cols);
  • Используйте тип size_t для представления размера объекта и количества массивов.

Обратите внимание, что код, который вы разместили, хранит, объявляет и обращается к матрицам по столбцам, что является вопросом соглашения, и это нормально (даже если OP, кажется, следует шаблону по строкам), но он также отпечатки матрицы в так же, и это может сбивать с толку.

Bob__ 28.05.2019 10:28

С этим кодом есть несколько проблем: он нечитаем и очень неудобен для кэширования, то есть медленный.

Что касается кеша, вы всегда должны перебирать самое внешнее измерение 2D-массива (не имеет значения, вызываете ли вы эту одну строку или столбец), и у вас должен быть только один вызов malloc в коде, так что вы получаете смежные Память. Если вы позволите компилятору вычислять индексы массива вместо того, чтобы делать это вручную, это также обычно повышает производительность.

Мы можем значительно упростить это, используя элемент гибкого массива в конце структуры, а затем используя его как «искаженный массив» старой школы всякий раз, когда мы к нему обращаемся. «Искаженный массив» означает, что тип массива является одномерным в синтаксисе C, но мы обращаемся к нему, как если бы это был двумерный массив.

Тип структуры будет:

typedef struct 
{
  char   name[50];
  size_t columns;
  size_t rows;
  float  data[];
} matrix_t;

И выделяем память под него один раз, одним вызовом:

matrix_t* matrix = malloc( sizeof *matrix + sizeof(float[c][r]) );

Затем при доступе к «искаженному массиву» мы можем привести указатель к типу 2D-массива и использовать его каждый раз, когда мы обращаемся к данным:

float (*data)[r] = (float(*)[r]) matrix->data;

Полный пример:

#include <stdlib.h>
#include <stdio.h>

typedef struct 
{
  char   name[50];
  size_t columns;
  size_t rows;
  float  data[];
} matrix_t;

int main (void)
{
  size_t c = 3;
  size_t r = 5;
  matrix_t* matrix = malloc(sizeof *matrix + sizeof(float[c][r]));

  float (*data)[r] = (float(*)[r]) matrix->data;
  for(size_t i=0; i<c; i++)
  {
    for(size_t j=0; j<r; j++)
    {
      data[i][j] = (float)i+j; // assign some random value
      printf("%.2f ", data[i][j]);
    }
    printf("\n");
  }

  free(matrix);
}

"Если вы позволите компилятору вычислять индексы массива вместо того, чтобы делать это вручную, это также обычно повышает производительность." У вас случайно нет примера, где это происходит? т.е. где компилятор не распознает обычную i*r+j индексацию и выдает худший кодеген?

Acorn 28.05.2019 15:13

@Acorn, это немного сложная тема, но если вы это сделаете i*r+j, вы просто скажете компилятору: «Выполните эту арифметику, затем используйте результат в качестве адреса», после чего он, скорее всего, будет делать то, что вы говорите, вслепую. Но если вместо этого вы используете вложенный цикл for, компилятор может заранее сказать, что будет делать код, и выполнить соответствующую оптимизацию. Например, развертывание цикла или переключение на итератор с обратным счетом и т. д.

Lundin 28.05.2019 15:17

Хотя, кажется, трудно воспроизвести. gcc -O2 сделал идентичный исходный код, когда я переписал вышеприведенное с помощью нотации i*r+j для искаженного одномерного массива. Как и clang, но в обоих случаях он разворачивал весь цикл. Думаю, я не могу доказать, что это более эффективно в некоторых обстоятельствах, но определенно более читабельно использовать версию синтаксиса 2D-массива.

Lundin 28.05.2019 15:27

Да, именно поэтому я и спрашивал - обычные случаи прекрасно обрабатываются всеми основными компиляторами, и у меня нет контрпримера :) Что касается читабельности, я предпочитаю другой вариант, чтобы использовать плоские указатели ( фактическое индексирование в любом случае может быть абстрагировано).

Acorn 28.05.2019 16:30

Другие вопросы по теме