How to save binary of multi-dimensional matrix in Matlab and load it to as a C++ struct?

Viewed 314

I have a multi-dimensional matrix in Matlab, I save it as a binary file like this:

mat = reshape(1:90, 9,5,2);
fileName = 'mat.bin';
fid = fopen(fileName, 'w');
fwrite(fid, mat, 'int32');
fclose(fid);

In C++ I load it like this:

struct Mat {
    int32_t arr[9][5][2];
};

std:ifstream input("mat.bin");
Mat* m = new Mat();
input.read(reinterpret_cast<char*>(m), sizeof(Mat));
input.close();

The problem is that the loaded data is not arranged the same way in memory for instance:

Accessing arr[1][1][1] will yield the value of the Matlab's array of indices mat(5,2,1)

(considering of course that Matlab's indices start at 1)

How can I arrange the matrix (and any other multi-dimensional matrix) before writing it to a binary file, in a way that arr[i][j][k] will be the same as mat(i+1, j+1, k+1)?

EDIT :

Current solution
For the 2-D case, Transpose of matrix solves it.

For x-D I found that I can change the order of vectors like this permute(mat, [D D-1 D-2 ... 1]) so it's like spreading the vectors (which matches how memory is mapped in C++) I still look for a more general code that doesn't have to write a vector of dimensions (which sometimes change)

Thanks.

2 Answers

It is always best to store arrays as plain 1D arrays and fixing how you access it. Your mat[5][2][1] is equivalent to mat[1 + 2*2 + 5*10]. When moving on to dynamic memory allocation, creating an array that is indexed as mat[5][2][1] implies allocating an array of pointers to arrays of pointers to arrays of data. This involves a huge overhead in memory allocation (which is quite expensive) and causes memory fragmentation and poor cache performance. Instead, allocating a single plain array that can hold the data, and computing the linear index from your 3 indices is quite simple and cheap.

This is a trivial implementation (without any form of checking) for a dynamically allocated 3D array:

struct Mat {
   using value_type = int32_t;
   std::vector<value_type> data;
   int size[3] = {0, 0, 0};
   Mat(int s1, int s2, int s3) {
      size[0] = s1; size[1] = s2; size[2] = s3;
      data.resize(s1 * s2 * s3);
   }
   value_type operator()(int i, int j, int k) {
      return data[(k * size[1] + j) * size[0] + i];
   }
};

Of course, you could make it into a class with appropriate data hiding, etc. Or you could just use one of the many libraries that exist that implement such arrays. There's an overloaded operator () for indexing: m(5,2,1) is translated to the appropriate indexing into the 1D array m.data. (I didn't bother here to return the indexed element by reference, but that would of course make more sense).


Here's a complete program to test it. With the input data being integers 1:90 in column-major storage order, in MATLAB I see this:

>> mat(6,3,2)
ans =  69

The C++ program outputs this:

m(5,2,1) = 69

Source code:

#include <vector>
#include <fstream>
#include <iostream>

struct Mat {
   using value_type = int32_t;
   std::vector<value_type> data;
   int size[3] = {0, 0, 0};
   Mat(int s1, int s2, int s3) {
      size[0] = s1; size[1] = s2; size[2] = s3;
      data.resize(s1 * s2 * s3);
   }
   value_type operator()(int i, int j, int k) {
      return data[(k * size[1] + j) * size[0] + i];
   }
};

int main() {
   std::ifstream input("mat.bin");
   Mat m(9, 5, 2);
   input.read(reinterpret_cast<char*>(m.data.data()), m.data.size() * sizeof(Mat::value_type));
   input.close();
   std::cout << "m(5,2,1) = " << m(5,2,1) << '\n';
}

You may use matC = permute(mat, ndims(mat):-1:1), before saving from MATLAB:

permute(mat, ndims(mat):-1:1), changes dimensions from (9,5,2) to (2,5,9) in your example.

It's basically your solution but replacing permute(mat, [D D-1 D-2 ... 1]) with ndims(mat):-1:1.


MATLAB code sample:

mat = ones(9,5,2);

% Fill some data for testing:
mat(:, :, 1) = meshgrid(1:5, 1:9);
mat(:, :, 2) = meshgrid(101:105, 101:109);

matC = permute(mat, ndims(mat):-1:1);

fileName = 'mat.bin';
fid = fopen(fileName, 'w');
fwrite(fid, matC, 'int32');
fclose(fid);

C++ code sample:

#include <stdint.h>
#include <iostream>
#include <fstream>

int main()
{
    struct Mat {
        int32_t arr[9][5][2];
    };

    std::ifstream input("mat.bin", std::ios_base::binary);
    Mat* m = new Mat();
    input.read(reinterpret_cast<char*>(m), sizeof(Mat));
    input.close();

    delete m;    

    return 0;
}

Testing sample: In C++: m->arr[3][4][1] = 105
In MATLAB: mat(4,5,2) = 105

Related