Is it possible return cell array that contains one instance in several cells?

前端 未结 2 1979
半阙折子戏
半阙折子戏 2020-12-18 06:53

I write some mex function and have to return huge array of strings.

I do this as following:

  mxArray * array = mxCreateCellMatrix(ARRAY_LEN, 1);
           


        
相关标签:
2条回答
  • 2020-12-18 07:23

    The second code you suggested is not safe and should not be used, as it could crash MATLAB. Instead you should write:

    mxArray *arr = mxCreateCellMatrix(len, 1);
    mxArray *str = mxCreateString("Hello");
    for(mwIndex i=0; i<len; i++) {
        mxSetCell(arr, i, mxDuplicateArray(str));
    }
    mxDestroyArray(str);
    plhs[0] = arr;
    

    This is unfortunately not the most efficient use of memory storage. Imagine that instead of using a tiny string, we were storing a very large matrix (duplicated along the cells).


    Now it is possible to do what you initially wanted, but you'll have to be resort to undocumented hacks (like creating shared data copies or manually increment the reference count in the mxArray_tag structure).

    In fact this is what usually happens behind the scenes in MATLAB. Take this for example:

    >> c = cell(100,100);
    >> c(:) = {rand(5000)};
    

    As you know a cell array in MATLAB is basically an mxArray whose data-pointer points to an array of other mxArray variables.

    In the case above, MATLAB first creates an mxArray corresponding to the 5000x5000 matrix. This will be stored in the first cell c{1}.

    For the rest of the cells, MATLAB creates "lightweight" mxArrays, that basically share its data with the first cell element, i.e its data pointer points to the same block of memory holding the huge matrix.

    So there is only one copy of the matrix at all times, unless of course you modify one of them (c{2,2}(1)=99), at which point MATLAB has to "unlink" the array and make a separate copy for this cell element.

    You see internally each mxArray structure has a reference counter and a cross-link pointer to make this data sharing possible.

    Hint: You can study this data sharing behavior with format debug option turned on, and comparing the pr pointer address of the various cells.

    The same concept holds true for structure fields, so when we write:

    x = rand(5000);
    s = struct('a',x, 'b',x, 'c',x);
    

    all the fields would point to the same copy of data in x..


    EDIT:

    I forgot to show the undocumented solution I mentioned :)

    mex_test.cpp

    #include "mex.h"
    
    extern "C" mxArray* mxCreateReference(mxArray*);
    
    void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
    {
        mwSize len = 10;
        mxArray *arr = mxCreateCellMatrix(len, 1);
        mxArray *str = mxCreateString("Hello");
        for(mwIndex i=0; i<len; i++) {
            // I simply replaced the call to mxDuplicateArray here
            mxSetCell(arr, i, mxCreateReference(str));
        }
        mxDestroyArray(str);
        plhs[0] = arr;
    }
    

    MATLAB

    >> %c = repmat({'Hello'}, 10, 1);
    >> c = mex_test()
    >> c{1} = 'bye'
    >> clear c
    

    The mxCreateReference function will increment the internal reference counter of the str array each time it is called, thus letting MATLAB know that there are other copies of it.

    So when you clear the resulting cell arrays, it will in turn decrement this counter one for each cell, until the counter reaches 0 at which point it is safe to destroy the array in question.

    Using the array directly (mxSetCell(arr, i, str)) is problematic because the ref-counter immediately reaches zero after destroying the first cell. Thus for subsequent cells, MATLAB will attempt to free arrays that have already been freed, resulting in memory corruption.

    0 讨论(0)
  • 2020-12-18 07:41

    Bad news ... as of R2014a (possibly R2013b but I can't check) mxCreateReference is no longer available in the library (either missing or not exported), so the link will fail. Here is a replacement function you can use that hacks into the mxArray and bumps up the reference count manually:

    struct mxArray_Tag_Partial {
        void *name_or_CrossLinkReverse;
        mxClassID ClassID;
        int VariableType;
        mxArray *CrossLink;
        size_t ndim;
        unsigned int RefCount; /* Number of sub-elements identical to this one */
    };
    
    mxArray *mxCreateReference(const mxArray *mx)
    {
        struct mxArray_Tag_Partial *my = (struct mxArray_Tag_Partial *) mx;
        ++my->RefCount;
        return (mxArray *) mx;
    }
    
    0 讨论(0)
提交回复
热议问题