Skip to content

Heinrich-Hertz-Lehrstuhl

für Informationstheorie und theoretische

Informationstechnik

Sections
Personal tools
You are here: Home » Members » Benjamin's Home » Matlab: How to modify a struct or cell array in a mex-file without using "mxDuplicateArray"

Matlab: How to modify a struct or cell array in a mex-file without using "mxDuplicateArray"

Document Actions
This article describes how to change a cell of a cell array or a field of a structure array in a mex-function without duplicating the whole array. Since the right-hand-side arguments must not be changed, one typically creates a deep copy of the array using the function "mxDuplicateArray" and changes the field in the copy. A more cunning way is to create a shared copy, making it unnecessary to copy the fields or cells not being changed.

The internal structure of how Matlab stores a variable as well as the concept of sharing variables using crosslinks are described in an article from Peter Boetcher.

For creating shared copies of an mxArray, the functions "mxCreateSharedDataCopy", "mxUnshareArray", and "mxUnreference" are used. Since not documented, the following might not be correct. But it has been tested and seems to work for Matlab versions R14, 2006b, 2007a. Any ideas about how doing it better are welcome.

Examine the disassembly, I guess that the three functions have the following syntax:
  mxArray *mxCreateSharedDataCopy(const mxArray *pr);
  bool mxUnshareArray(const mxArray *pr, const bool noDeepCopy);    // true if not successful
  mxArray *mxUnreference(const mxArray *pr);

They are described in the following. The const declaration is not fully correct since the array headers change. But with it, the compiler does not complain about different const declarations when assigning right-hand-side arguments.


Take a variable x created in Matlab:

  x = {[1 2], [3 4]};

In a C-mex-file, this cell array looks as follows. "x" points to an mxArray whose data pointer "pdata" points to an array of two pointers, each pointing to an mxArray (an mxArray is indicated by a rectangle):

mxArray *x;

*x:
+==================+
| crosslink = NULL |
| refcount = 0     |
| pdata  ----------+------> [mxArray*                 mxArray*]
+==================+             |                       |
                                \ /                     \ /
                          +==================+    +==================+
                          | crosslink = NULL |    | crosslink = NULL |
                          | refcount = 0     |    | refcount = 0     |
                          | pdata  -+        |    | pdata  -+        |
                          +=========|========+    +=========|========+
                                    |                       |
                                   \ /                     \ /
                                  [1 2]                   [3 4]

If we want to change the content of the first cell, we first must create a copy of x. The following procedere is equivalent for structure arrays, except the mxGetCell and mxSetCell functions must be replaced by the accordant functions for structure arrays.

  mxArray *y = mxCreateSharedDataCopy(x);

The data pointer of y points to the same array as x does. A crosslink is set between x and y. Hence, only the array header is copied, the actual data is not:

*x:
+==================+
| crosslink = y    |
| refcount = 0     |
| pdata  ----------+------> [mxArray*                 mxArray*]
+==================+  / \        |                       |
                       |        \ /                     \ /
                       |  +==================+    +==================+
                       |  | crosslink = NULL |    | crosslink = NULL |
                       |  | refcount = 0     |    | refcount = 0     |
                       |  | pdata  -+        |    | pdata  -+        |
                       |  +=========|========+    +=========|========+
                       |            |                       |
                       |           \ /                     \ /
                       |          [1 2]                   [3 4]
*y:                    |
+==================+   |
| crosslink = x    |   |
| refcount = 0     |   |
| pdata  ----------+---+
+==================+

To be able to modify a cell of y, we must unshare both arrays:

 mxUnshareArray(y, true);      // true: do not make a deep copy

The array of mxArray-pointers is copied and the crosslink between x and y is removed. The reference counter (refcount) of each mxArray in the cell is increased by one. The data is still shared.

*x:
+==================+
| crosslink = NULL |
| refcount = 0     |
| pdata  ----------+------> [mxArray*                 mxArray*]
+==================+          |                       |
                             \ /                     \ /
                          +==================+    +==================+
              *first_cell:| crosslink = NULL |    | crosslink = NULL |
                          | refcount = 1     |    | refcount = 1     |
                          | pdata  -+        |    | pdata  -+        |
                          +=========|========+    +=========|========+
                             / \    |                / \    |
                              |    \ /                |    \ /
                              |   [1 2]               |   [3 4]
*y:                           |                       |
+==================+          |                       |
| crosslink = NULL |          |                       |
| refcount = 0     |          |                       |
| pdata  ----------+------> [mxArray*             mxArray*]
+==================+

Now we want to change the first cell of y. The shared mxArray "first_cell" has to be copied using the function "mxUnreference". Get the first mxArray:

  mxArray *first_cell = mxGetCell(y, 0);

Note that the following yields the same result, since the mxArray only exists once (y is replaced by x):

  mxArray *first_cell = mxGetCell(x, 0);

The mxUnreference function copies the mxArray and creates a crosslink between the copies. The affected reference counter is decreased by 1:

mxArray *first_cell_copy = mxUnreference(first_cell);
*x:
+==================+
| crosslink = NULL |
| refcount = 0     |
| pdata  ----------+------> [mxArray*                 mxArray*]
+==================+          |                       |
                             \ /                     \ /
                          +==================+    +==================+
              *first_cell:| crosslink = f_c_c|    | crosslink = NULL |
                          | refcount = 0     |    | refcount = 1     |
                          | pdata  -+        |    | pdata  -+        |
                          +=========|========+    +=========|========+
                                    | <-----+        / \    |
                                   \ /      |         |    \ /
                                  [1 2]     |         |   [3 4]
*y:                                         |         |
+==================+                        |         |
| crosslink = NULL |                        |         |
| refcount = 0     |                        |         |
| pdata  ----------+------> [mxArray*       |     mxArray*]
+==================+          |             |
                             \ /            |
                          +=================|+
         *first_cell_copy:| crosslink = f_c ||
                          | refcount = 0    ||
                          | pdata  ---------+|
                          +==================+

To assign a new value to y{1}, the crosslink between first_cell and first_cell_copied must be removed. This can be done by either destroying the mxArray "first_cell_copy" and assigning a new one to y{1}:

  mxDestroyArray(first_cell_copy);
  mxSetCell(y, 0, new_mxArray);

or by unsharing it and assigning the new data pointer:

  mxUnshareArray(first_cell_copy);
  mxSetData ...

If first_cell is another cell or structure array, one can recursively repeat all the steps above (after mxCreateShareCopy). It is noteworthy that all this can also be applied on objects, since objects in Matlab are stored as structure arrays.

The steps above are equivalent to the following Matlab-Code:

y = x;
y{1} = ...

In a first version I used the function "mxCreateSharedCopy" instead of "mxCreateSharedDataCopy". In most cases, these functions behave equally. They do not if the mxArray to be copied is marked as being a temporal array. This is the case if the result of an expression, and not a variable, is used as the argument of a function. For instance, in the statement

y = foo(x{1}, x);

the first argument given to the function "foo" is a temporal mxArray, while the second one is not. In such a case, calling p2 = mxCreateSharedCopy(p1) (where p1 is a pointer to the mxArray x{1}) does not actually create a shared copy, it rather returns the given pointer to the mxArray. That is, p2 is equal to p1. This can cause problems because the temporal array gets destroyed when the function is finished; hence, the pointer to this array is not valid anymore. Using the function "mxCreateSharedDataCopy" instead works as intended. It really creates a shared copy and the mxArray does not get destroyed.


Suggestions for improvements are welcome. I hope the infos are useful to someone. In my case, it gives a great performance benefit when modifying a certain property of a large number of objects. Below is a program demonstrating the use of the three functions.

/************************************************************************
 * syntax in Matlab:
 * function var_struct_copied = foo(var_struct, value, duplicate_array)
 *
 * Returns the structure variable "var_struct" with the first field set to
 * "value". If "duplicate_array" is false or omitted, a shared copy is
 * created. Otherwise a deep copy of the struct array is returned
 * (using "mxDuplicateArray").
 *************************************************************************/

#include "mex.h"

// declare undocumented functions
extern mxArray *mxCreateSharedDataCopy(const mxArray *pr);
extern bool mxUnshareArray(const mxArray *pr, const bool noDeepCopy);
extern mxArray *mxUnreference(const mxArray *pr);


/* Definition of structure mxArray_tag for debugging purposes. Might not
* be fully correct for Matlab 2006b or 2007a, but the important things
* are. Thanks to Peter Boettcher.
*/
struct mxArray_tag {
  const char *name;
  mxClassID class_id;
  int vartype;
  mxArray    *crosslink;
  int      number_of_dims;
  int      refcount;
  struct {
    unsigned int    scalar_flag : 1;
    unsigned int    flag1 : 1;
    unsigned int    flag2 : 1;
    unsigned int    flag3 : 1;
    unsigned int    flag4 : 1;
    unsigned int    flag5 : 1;
    unsigned int    flag6 : 1;
    unsigned int    flag7 : 1;
    unsigned int    private_data_flag : 1;
    unsigned int    flag8 : 1;
    unsigned int    flag9 : 1;
    unsigned int    flag10 : 1;
    unsigned int    flag11 : 4;
    unsigned int    flag12 : 8;
    unsigned int    flag13 : 8;
  }   flags;
  int  rowdim;
  int  coldim;
  union {
    struct {
      double  *pdata;       // original: void*
      double  *pimag_data;  // original: void*
      void *irptr;
      void  *jcptr;
      int   nelements;
      int   nfields;
    }   number_array;
    struct {
      mxArray **pdata;
      char  *field_names;
      void  *dummy1;
      void  *dummy2;
      int   dummy3;
      int   nfields;
    }   struct_array;
    struct {
      void *pdata;  /*mxGetInfo*/
      char *field_names;
      char *name;
      int checksum;
      int  nelements;
      int  reserved;
    }  object_array;
  }   data;
};


// Matlab gatway function
void mexFunction(int nlhs, mxArray *plhs[],
                 int nrhs, const mxArray *prhs[])
{
    const mxArray *p_in,
                  *mx_value;        // value to be assigned
    mxArray *p_return;              // mxArray to be returned
    bool duplicate_array = false;   // duplicate or shallow?


    if (nrhs < 2) {
        mexErrMsgTxt("Two input arguments are expected.");
    } else {
        p_in = prhs[0];
        mx_value = prhs[1];
        if (!mxIsStruct(p_in))
            mexErrMsgTxt("First argument must be a structure array.");
    }

    if (nrhs == 3) {
        const mxArray *mx_arg = prhs[2];
        if (mxIsLogicalScalar(mx_arg)) {
            duplicate_array = *mxGetLogicals(mx_arg);
        } else
            mexErrMsgTxt("Third argument must be a logical scalar.");
    }

    if (duplicate_array) {
        // duplicate given arrays, set value and return
        p_return = mxDuplicateArray(p_in);
        mxSetFieldByNumber(p_return, 0, 0, mxDuplicateArray(mx_value));

    } else {
        mxArray *mx_field, *mx_field_shared;

        // create shared copy
        p_return = mxCreateSharedDataCopy(p_in);

        // unshare array
        mxUnshareArray(p_return, true);

        // unreference first field
        mx_field = mxGetFieldByNumber(p_return, 0, 0);
        mx_field_shared = mxUnreference(mx_field);
        // mxArray *mx_field has been copied and is shared (crosslinked) with *mx_field_shared

        // destroy shared array (removes crosslink), since we want to set a new one
        mxDestroyArray(mx_field_shared);

        // set new array
        mxSetFieldByNumber(p_return, 0, 0, mxCreateSharedDataCopy(mx_value));
    }

    plhs[0] = p_return;
}


Contributors : Benjamin Schubert
Last modified 09.12.2007 23:47
 

Powered by Plone