5

我写了一些 mex 函数并且必须返回大量的字符串。

我这样做如下:

  mxArray * array = mxCreateCellMatrix(ARRAY_LEN, 1);
  for (size_t k = 0; k < ARRAY_LEN; ++ k) {
      mxArray *str = mxCreateString("Hello");
      mxSetCell(array, k, str);
  }
  prhs[0] = array;

但是,由于字符串始终具有相同的值,因此我只想创建它的一个实例。喜欢

  mxArray * array = mxCreateCellMatrix(ARRAY_LEN, 1);
  mxArray *str = mxCreateString("Hello");

  for (size_t k = 0; k < ARRAY_LEN; ++ k) {
      mxSetCell(array, k, str);
  }
  prhs[0] = array;

有可能吗?垃圾收集器如何知道释放它?谢谢你。

4

4 回答 4

6

您建议的第二个代码不安全,不应使用,因为它可能会使 MATLAB 崩溃。相反,你应该写:

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;

不幸的是,这不是内存存储的最有效使用。想象一下,我们不是使用一个小字符串,而是存储一个非常大的矩阵(沿单元格复制)。


现在可以执行您最初想要的操作,但您必须求助于未记录的 hack(例如创建共享数据副本或手动增加mxArray_tag 结构中的引用计数)。

事实上,这就是 MATLAB 幕后通常发生的事情。以此为例:

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

如您所知,MATLAB 中的元胞数组基本上是一个mxArray其数据指针指向其他mxArray变量数组的数组。

在上面的例子中,MATLAB 首先创建一个mxArray对应于 5000x5000 的矩阵。这将存储在第一个单元格中c{1}

对于其余的单元格,MATLAB 创建“轻量级” mxArrays,基本上与第一个单元格元素共享其数据,即它的数据指针指向保存巨大矩阵的同一块内存。

因此,始终只有一个矩阵副本,除非您当然修改其中一个 ( c{2,2}(1)=99),此时 MATLAB 必须“取消链接”该数组并为此单元元素制作单独的副本。

您会在内部看到每个mxArray结构都有一个引用计数器和一个交叉链接指针,以使这种数据共享成为可能。

提示format debug:您可以通过 打开选项来研究这种数据共享行为,并比较pr各个单元格的指针地址。

同样的概念也适用于结构字段,所以当我们编写时:

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

所有字段都将指向x..中的相同数据副本


编辑:

我忘了展示我提到的无证解决方案:)

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

该函数将在每次调用mxCreateReference时递增数组的内部引用计数器,从而让 MATLAB 知道它还有其他副本。str

因此,当您清除生成的单元格数组时,它会依次为每个单元格递减该计数器,直到计数器达到 0,此时可以安全地销毁相关数组。

直接使用数组 ( mxSetCell(arr, i, str)) 是有问题的,因为 ref-counter 在破坏第一个单元后立即变为零。因此,对于后续单元,MATLAB 将尝试释放已释放的数组,从而导致内存损坏。

于 2013-09-17T11:51:45.333 回答
3

坏消息......截至 R2014a(可能是 R2013b,但我无法检查)库中不再提供 mxCreateReference(丢失或未导出),因此链接将失败。这是一个替换函数,您可以使用它侵入 mxArray 并手动增加引用计数:

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;
}
于 2015-03-11T02:58:56.100 回答
0

@Jimbo,对您发布的代码的一些评论:

您的代码默认运行的是 64 位 MATLAB 版本,并且 mwSize 是 64 位的。如果这用于 32 位 MATLAB 版本并且 mwSize 是 32 位,那么您计算的 ref_count 位置将不正确。

如果没有您正在使用的库函数的必要标头,代码将无法正常工作。即,在没有原型的 C 中,返回 float 的函数将被假定为返回 int,计算结果最终会出错。也许在顶部包含这些行以使其明确:

#include <stdlib.h>  /* strtof */
#include <math.h> /* roundf */

我看不到任何逻辑,例如,您将“添加 0”到个位数分数以使 9.9 看起来小于 9.12,例如您所指出的。例如,9.12 只会导致minor_ver 为1,而不是您所指出的12。这应该解决。

mexCallMATLAB从头开始​​创建返回 mxArray。您不需要“预分配”结果。实际上,您所做的只是造成内存泄漏,因为指向 mxCreateNumericMatrix(etc) 调用的指针被 mexCallMATLAB 调用覆盖。解决方案只是定义返回变量,仅此而已。例如,

mxArray *version;

您应该释放用于计算版本号的临时内存。是的,这些将在垃圾收集列表中(在 R2017a 之前,str不会在垃圾收集列表中),但最好在完成后立即释放内存。例如,在计算 ref_offset 之后,执行以下操作:

mxDestroyArray(version);
mxFree(str);

mxArray 的 ref_count 字段是一个 32 位整数。它紧挨着另一个用于位标志(isComplex、isNumeric、isSparse 等)的 32 位整数。但是,您指向 ref_count,就好像它是一个 64 位整数 mwSize,然后基于此递增它。如果实际的 32 位 ref_count 恰好与 64 位 mwSize 的低 32 位对齐,这可能会起作用,但这有点不确定 IMO,因为它似乎取决于 64 位整数的字序。您可能需要修改它以使其更健壮。

您可能还对此处发布的 MATLAB 版本代码(编译时和运行时)感兴趣: https ://www.mathworks.com/matlabcentral/fileexchange/67016-c-mex-matlab-version

于 2021-02-14T23:31:45.543 回答
0

在 2019b 中,参考计数器的位置发生了移动。作为一种解决方法,我现在在运行时检测 MATLAB 版本,并相应地将偏移量更改为标头。也可以进行编译时检查,但我希望我的 mex 文件能够跨版本工作而无需重新编译。请注意,由于我不再显式访问结构,因此我不再有部分结构定义。在编译时,我还向用户公开了一个标志选项ALLOW_REF_COUNT以简单地进行深度复制。欢迎反馈/建议...

#include "stdlib.h"  /* atoi */
#include "string.h" /* strchr */
int ref_offset = -1;

mxArray* mxCreateReference(const mxArray *mx){
    #ifdef ALLOW_REF_COUNT
        if (ref_offset == -1){
            //Grabs output of version() e.g. 9.9.0.15 etc.
            //and translates into 909 - we add a 0 because we would want
            //9.12 to be 912 and newer/higher than 9.9
            mxArray *version;
            mexCallMATLAB(1,&version,0, NULL, "version");
            char *str = mxArrayToString(version);
            char* loc = strchr(str, '.');
            int mantissa = atoi(loc+1);
            int whole = atoi(str);
            int version_id = whole*100 + mantissa;

            mxDestroyArray(version);
            mxFree(str);
            
            //_Static_assert => c11
            _Static_assert(sizeof(void *) == 8, "Error: 32bit MATLAB not supported");
            
            //907 -> 2019b
            if (version_id < 907){
                ref_offset = 8;
            }else{
                ref_offset = 6;
            }
        }

        uint32_t *ref_count = ((uint32_t *) mx) + ref_offset; 
        (*ref_count)++;

        //struct mxArray_Tag_Partial *my = (struct mxArray_Tag_Partial *) mx;
        //++my->RefCount;
        return (mxArray *) mx;
    #else
        return mxDuplicateArray(mx);
    #endif
}
于 2021-02-14T04:24:26.410 回答