2

The document

http://www.mathworks.com/help/matlab/math/resizing-and-reshaping-matrices.html#f1-85977

suggested that we can do

A(2:2:10) = []

to shrink a matrix. Since it requests shifting the rest elements to construct a compact matrix, in worst case, the complexity must be linear. But I assume it would not reallocate memory. Correct? If I remove only continuous elements at the tail, can we assume that it does not do any complicated work?

Joe C
  • 2,757
  • 2
  • 26
  • 46

1 Answers1

3

You can check yourself using a little helper MEX-function (or the undocumented format debug mode):

dump.c

#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    if (nrhs != 1 || nlhs > 0)
        mexErrMsgIdAndTxt("dump:error", "Wrong number of arguments.");
    if (!mxIsNumeric(prhs[0]))
        mexErrMsgIdAndTxt("dump:error", "Expects a numeric array.");
    mexPrintf("header = 0x%p\n", prhs[0]);
    mexPrintf("data   = 0x%p\n", mxGetData(prhs[0]));
}

Now:

>> A = 1:5
A =
     1     2     3     4     5
>> dump(A)
header = 0x00000000148312F0
data   = 0x000000007C97CEC0

>> A(5) = []
A =
     1     2     3     4
>> dump(A)
header = 0x00000000148312F0
data   = 0x000000007CEBA840

Similarly:

>> A = 1:5
A =
     1     2     3     4     5
>> dump(A)
header = 0x000000001482A400
data   = 0x000000007DDC7740

>> A = A(1:4)
A =
     1     2     3     4
>> dump(A)
header = 0x000000001482A400
data   = 0x000000007D7C9C60

In general, doing any array slicing will reallocate data (except for the special case of A=A(:) where only the size in the header is changed without touching the data).


EDIT 1:

Now consider this MEX-function:

droplastcol.c

#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    mwSize N;
    if (nrhs != 1 || nlhs > 0)
        mexErrMsgIdAndTxt("dump:error", "Wrong number of arguments.");
    if (mxIsSparse(prhs[0]) || !mxIsNumeric(prhs[0]) || mxGetNumberOfDimensions(prhs[0])>2)
        mexErrMsgIdAndTxt("dump:error", "Expects a dense numeric matrix.");
    N = mxGetN(prhs[0]);
    if (N > 0)
        mxSetN((mxArray*)prhs[0], N-1);
}

It will drop the last column of a matrix without reallocating the data:

>> A = 1:5
A =
     1     2     3     4     5
>> dump(A)
header = 0x0000000014829FA0
data   = 0x000000007D4E1000

>> droplastcol(A)

>> A
A =
     1     2     3     4
>> dump(A)
header = 0x0000000014829FA0
data   = 0x000000007D4E1000

Using mxSetN this way is completely safe with no memory leaks (it will even play nice with "data sharing" and "lazy copy-on-write", e.g A=1:5;B=A;droplastcol(A);). To quote the docs:

If calling mxSetN reduces the number of elements in the mxArray, you might want to reduce the sizes of the pr, pi, ir, and/or jc arrays to use heap space more efficiently. However, reducing the size is not mandatory.

Meaning that the heap memory allocated for the data is still claimed as used, but it won't leak when the array is destroyed (clear A).

NOTE: The above code was tested in MATLAB R2014a. It seems that something changed in recent releases, and the droplastcol MEX-function does not work correctly in newer versions (I tried it in R2015b and the size isn't affected when the function returns!).


EDIT2

It seems on newer MATLAB versions, we have to use the undocumented mxCreateSharedDataCopy. Here is the modified MEX-function:

droplastcol.c

#include "mex.h"

EXTERN_C mxArray* mxCreateSharedDataCopy(const mxArray*);

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    mwSize N;
    if (nrhs != 1 || nlhs > 1)
        mexErrMsgIdAndTxt("dump:err", "Wrong number of arguments.");
    if (mxIsSparse(prhs[0]) || !mxIsNumeric(prhs[0]) || mxGetNumberOfDimensions(prhs[0])>2)
        mexErrMsgIdAndTxt("dump:err", "Expects a numeric matrix.");
    plhs[0] = mxCreateSharedDataCopy(prhs[0]);
    N = mxGetN(plhs[0]);
    if (N > 0)
        mxSetN((mxArray*)plhs[0], N-1);
}

Now we test it in R2015b:

>> mex -largeArrayDims droplastcol2.c
Building with 'Microsoft Visual C++ 2010 (C)'.
MEX completed successfully.

>> A = 1:5
A =
     1     2     3     4     5
>> dump(A)
header = 0x00000000693A0B80
data   = 0x00000000943A5D60

>> B = droplastcol2(A)
B =
     1     2     3     4
>> A
A =
     1     2     3     4     5

>> dump(A)
header = 0x00000000693A1600
data   = 0x00000000943A5D60
>> dump(B)
header = 0x00000000693A0C60
data   = 0x00000000943A5D60

You could have also done: A = droplastcol2(A), the data will not be reallocated, only a new header will be created pointing to the same data in memory.

Amro
  • 123,847
  • 25
  • 243
  • 454
  • @Armo Thanks for the measurement. It means the only way to reduce sizes w/o reallocation is writing our own mex functions. No existing matlab APIs do so... – Joe C Mar 19 '16 at 05:49
  • That's some nice detective work. Thanks for sharing. +1. – rayryeng Mar 19 '16 at 06:29
  • BTW, I didn't know about `format debug`. That's a great tool to use for debugging (don't mind the pun). Just a question. What does `pi` stand for when you observe the variable's verbose output in this mode? I'm assuming `pr` is the address of where the variable is being occupied in memory and `m` and `n` are the respective rows and columns of the variable. – rayryeng Mar 19 '16 at 06:43
  • thansk. `pr` stands for "pointer to real data", and `pi` pointer to imaginary data (in case of complex array, otherwise it is set to null). These correspond to `mxGetPr` and `mxGetPi` in the MEX API. `ir` and `jc` are used in sparse matrices only. – Amro Mar 19 '16 at 06:49
  • @Amro Ah of course. I should have known it was with reference to the MEX API. Thanks a bunch! – rayryeng Mar 19 '16 at 07:18
  • @Armo Is mxSetN only available in C API? Does m-code have a similar function? I did not find one from doc. – Joe C Mar 19 '16 at 20:41
  • 2
    @JoeC unfortunately yes, this is a specialized function only available on the C/C++ side.. – Amro Mar 19 '16 at 22:09
  • @Amro. I found droplastcol.c does not change A's contents in my MATLAB 16a: A's contents are unchanged. I guess this is because phr is declared as const (indicated by mex compile warning)? It works if I do "plhs[0] = phrs[0]; mxSetN(phs[0], N);" At this case it returns a vector with different header but the same data. The returned vector's contents are shorter than A. But two vectors share the same data. Is it safe? although 'clear all' does not complain anything (I am not sure if 'clear' frees memory lazily). – Joe C Mar 22 '16 at 16:18
  • @JoeC hmm.. The `const` thing is easily solved by an explicit cast (see the edit), but you're right about newer MATLAB versions. It didn't change the size in R2015b, although it worked in R2014a! Carefull that your proposed change is not safe. You can't just assign `plhs[0] = phrs[0];`... I tried to keep it simple here, but I guess the solution would be to use the undocumented `mxCreateSharedDataCopy`, see http://stackoverflow.com/q/19813718/97160 (especially read the comments). – Amro Mar 23 '16 at 02:39
  • 1
    @JoeC I edited my answer to include the solution I talked about using `mxCreateSharedDataCopy` – Amro Mar 23 '16 at 02:53