2

I'm trying to wrap my head around mexMakeMemoryPersistent() from MATLAB's C MEX API.

I don't understand - when a MEX file is called more than once, with mexMakeMemoryPersistent() having been used, how is the memory it's assigned given back to the MEX file when it's called a second time?

In example, let's say I have a MEX file called myFunc

And it contains the following:

int* myVar = NULL;

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) 
{
    if(myVar == NULL) 
    {
        myVar = (int*)mxCalloc(sizeof(int*), 10);
        mexMakeMemoryPersistent(myVar);
        myVar[0] = 1;
    }

    // Do the thing I want with it
    myVar[0] *= 2;
}

The first time we do

fx>> myFunc()

Inside MATLAB, it's obvious that myVar will be NULL, and then mxCalloc will grab some memory and get a pointer to it, etc.

But what about after it returns, and then gets called a second time:

fx>> myFunc()

We told MATLAB not to destroy the memory we just allocated for myVar. But how does that pointer get re-assigned into myVar when the new instance of myFunc is run? If it didn't, myVar would just be NULL and we'd be back at square one. Does it maintain a list of variable names and the memory that was assigned to them? But what happens if we have odd scoping rules, inheritance (in the case of C++) etc? How does it resolve what variable needs to be handed that pointer, and actually do the handoff?

I'm not having trouble using this, I just want to understand it conceptually because I think it's really neat.

Cris Luengo
  • 55,762
  • 10
  • 62
  • 120
Tyler Shellberg
  • 1,086
  • 11
  • 28

2 Answers2

3

MEX-files are loaded into memory the first time they’re called. At this point, global variables in the MEX-file get a location in memory and are initialized. Next mexFunction is called, where your code gets a chance to allocate memory and assign the pointer to this global variable.

The next time you call the MEX-file, it is still loaded in memory, and those global variables are still there. This time MATLAB just needs to call mexFunction.

When you do clear mex or clear all in MATLAB, the MEX-file will be unloaded from memory. The global variables will stop existing at this point. Because you used mxCalloc to allocate memory, MATLAB can reclaim the lost memory. If you had used calloc instead, you would have leaked memory at this point. You no longer have a pointer to the allocated memory, so you have leaked memory at this point (see James' answer below).

The next time you call the MEX-file, it will be like the first time you called it.


Note that, since a MEX-file is a compiled binary, your variable names are no longer visible (except in debug information). The machine code just deals with memory addresses and registers.

In the case of C++, scoping rules, inheritance, etc are all just abstractions that lead to the same machine code you can obtain with C or any other compiled language.


A few cases to clarify things:

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) 
{
    static int* myVar = NULL; // This is basically the same as a global myVar in this case
    if(myVar == NULL) 
    {
        myVar = (int*)mxCalloc(sizeof(int*), 10);
        //mexMakeMemoryPersistent(myVar); // Let's leave out this line!
        myVar[0] = 1;
    }

    myVar[0] *= 2;
}

In the case above, we didn't make the memory persistent. The myVar pointer is preserved across MEX-file calls, but not the memory pointed to. The 2nd time you call the MEX-file, myVar[0] *= 2 will do something illegal and likely crash MATLAB.

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) 
{
    int* myVar = NULL; // This is now a local variable
    if(myVar == NULL) 
    {
        myVar = (int*)mxCalloc(sizeof(int*), 10);
        mexMakeMemoryPersistent(myVar);
        myVar[0] = 1;
    }

    myVar[0] *= 2;
}

In the case above, myVar will be NULL every time the MEX-file is called, so every time new memory will be allocated. The memory is persistent, so eventually you will run out of memory with this.

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) 
{
    static int* myVar = NULL;
    if(myVar == NULL) 
    {
        myVar = (int*)malloc(sizeof(int*), 10); // Using the system malloc
        myVar[0] = 1;
    }

    myVar[0] *= 2;
}

In the case above, things are OK, except that the memory allocated by malloc is never freed. When you do clear all or clear mex, the MEX-file will be cleared, the myVar static variable will be deleted, but the memory allocated by malloc is still there. You are leaking memory again. If you want to do it this way, you need to register a function to be run when the MEX-file exists, using mexAtExit().

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) 
{
    static int myVar[10] = {1};
    myVar[0] *= 2;
}

In the case above, we use a static variable to hold all our data, no dynamic memory allocation is used, we don't need to worry about leaking memory. I recommend this unless the array is very large.

Cris Luengo
  • 55,762
  • 10
  • 62
  • 120
  • Thanks! I didn't realize that it stayed in memory after it completed, I assumed the MEX files were wiped, but that makes sense because otherwise ```clear mex``` wouldn't do much. But the rest of the state of the MEX file is lost, right? Like local variables and anything global that wasn't told to be ```mexMemoryMakePersistent()```, and any other state of the program, right? – Tyler Shellberg Jun 13 '19 at 15:03
  • @Tyler: `mexMemoryMakePersistent` is to tell MATLAB to not automatically free memory created with `mxCalloc` and the like. MATLAB otherwise automatically cleans up all memory allocated. If you allocate something with `malloc`, then MATLAB doesn't know anything about it and will not clean it up -- it is automatically persistent. – Cris Luengo Jun 13 '19 at 15:31
  • Then there's the difference between global variables (those defined outside the scope of any function), static variables (those declared [`static`](https://stackoverflow.com/questions/572547/what-does-static-mean-in-c) inside a function scope) and local variables (all other variables declared inside a function). Of these, the latter type does not persist between function calls, and hence don't persist between calls to your MEX-file. The other two types do persist. The normal scoping rules for C apply here, think of MATLAB as `main` and `mexFunction` as a function in your program. – Cris Luengo Jun 13 '19 at 15:31
  • So does that mean that if someone has say, a normal global c++ ```int```, it'll stick around between calls to the MEX file? Currently, to maintain such an int, I'm actually mxMalloc'ing space for it and making it persistent. If I don't need to do that, that'd be great. – Tyler Shellberg Jun 13 '19 at 16:20
  • @Tyler: Yes, either a global `int` outside the function body or a `static int` inside the function body will be preserved between function calls, at least until `clear mex` is called. You should look at the docs for `mexLock` to learn about how to prevent the MEX-file from being cleared. [Here](https://github.com/DIPlib/diplib/blob/4d9bb512121bdd90b1f14b7cb0e681aa9a34d8f8/dipimage/private/imagedisplay.cpp#L145) is a rather complex example, but I declare some `static` objects of complex C++ classes inside `mexFunction`, no `mxMalloc` is involved. – Cris Luengo Jun 13 '19 at 16:27
  • Alright. So mxAlloc, mxCalloc and similar are only useful inside a MEX file if you want to use MATLAB types? And mexMemoryMakePersistent is only useful if you want to make something allocated by either of those neither a global nor a static? – Tyler Shellberg Jun 13 '19 at 17:47
  • @Tyler: No, `mxCalloc` and the like are useful if you don't want to bother with manually freeing the memory allocated. MATLAB takes care of that for you. If you use `malloc` and the like instead, you need to make sure to also call `free`. If these are supposed to be persistent, then you also need to write a `mexAtExit` function. – Cris Luengo Jun 13 '19 at 18:02
  • @Tyler: `mexMakeMemoryPersistent` needs to be used in combination with a global or static variable to hold the pointer, otherwise the memory will still be around but you will not be able to access it. If you assign a `mxCalloc`ed memory pointer to a static variable, the pointer will live across MEX calls, but the memory won't, so the pointer will be invalid. – Cris Luengo Jun 13 '19 at 18:02
  • So using both mxCalloc/mxMalloc along with mexMakeMemoryPersistent would be pointless - as the convenience of the memory being automatically cleaned up would be lost by forcing you to make a mexAtExit() method to do an mxFree(). So in sum - if you don't need things to be persistent between calls and want it cleaned up automatically - use mxMalloc and mxCalloc. If you *do* want it to be persistent, than just make it static or global. Just make sure to have a mexAtExit function if there's a global/staci pointing to malloc'd memory though, so it'll be cleaned up on ```clear mex```. Is that right? – Tyler Shellberg Jun 13 '19 at 18:10
  • Ah, thanks. That makes sense. Appreciate all the info! – Tyler Shellberg Jun 13 '19 at 18:44
  • 1
    @Chris: Your statements about mxCalloc and friends never leaking are not true. See my Answer and example. – James Tursa Jun 14 '19 at 20:08
1

There seems to be some confusion about how mexMakeMemoryPersistent and mexMakeArrayPersistent work that I would like to clear up. DISCLAIMER: The following is based on behavior I have observed from tests I have performed ... not necessarily on official MATLAB documentation.

mxArray variable headers have a field which I will call VariableType that contains a value indicating the type of variable it is. E.g., for R2018b and earlier:

struct mxArray_header {
    void *RevCrossLink;
    mxClassID ClassID;
    int VariableType;
    :
    etc.

For variables in the workspace, VariableType will be 0 (normal). Typically, variables passed into prhs[ ] will be normal types.

All(*) of the official API functions that allocate memory (either mxArray variables or raw memory) put the address of that memory on the MATLAB Memory Manager temporary allocation list for the mex routine. NOTE: The "data" memory of an mxArray variable (i.e., the stuff behind the mxGetPr( ) and friends) is not on this allocation list ... its disposition is entirely dependent on the mxArray it is a part of. The VariableType of mxArrays created with official API functions is 4 (temporary).

(*) mxArrayToString( ) used to be an exception to this, but that was fixed in R2017a.

When the mex routine exits, as near as I can tell the following things happen:

  1. Shared data copies of the plhs[ ] variables are created (these are what are actually passed back to the caller).

  2. Everything on the temporary allocation lists for this mex routine are destroyed/freed.

With that backdrop, here is what the persistent functions do:

mexMakeMemoryPersistent(memory_address)

  • Removes memory_address from the temporary memory allocation list

mexMakeArrayPersistent(mxArray_address)

  • Removes mxArray_address from the temporary mxArray allocation list
  • Changes the VariableType of the mxArray behind mxArray_address to 0 (normal)

And, in fact, the documentation for mexMakeMemoryPersistent states the following:

"If you create persistent memory, you are responsible for freeing it when the MEX function is cleared. If you do not free the memory, MATLAB leaks memory."

Bottom line is, you must manually destroy/free persistent memory ... the MATLAB Memory Manager will no longer help you once you make the memory persistent. This is particularly true when the mex function is cleared from memory. Your persistent memory will be leaked regardless of the fact that you had a global variable storing it and regardless of the fact that it originally came from an official MATLAB API function. You need to use some combination of mexAtExit and/or mexLock/mexUnlock functions to manage this situation so that you don't have a memory leak. This has always been the behavior of these persistent functions to my knowledge.

SIDE NOTE: There are no official API functions to do the reverse ... i.e., you cannot make persistent memory non-persistent again. Once you make something persistent, you are stuck with it and must deal with it manually.

A demonstration with a 100MB memory block:

/* persist_test.c */
#include "mex.h"
char *cp = NULL;
#define ONE_MB (1024*1024)
void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    if( cp == NULL ) {
        cp = mxMalloc(100*ONE_MB);
        mexMakeMemoryPersistent(cp);
    }
}

And at the command line, clearly showing the memory leaking even when the mex routine is cleared from memory (memory usage constantly going up and never going down):

>> memory
Maximum possible array:        2324 MB (2.436e+09 bytes) *
Memory available for all arrays:        2324 MB (2.436e+09 bytes) *
Memory used by MATLAB:        1012 MB (1.061e+09 bytes)
Physical Memory (RAM):        8056 MB (8.447e+09 bytes)

*  Limited by System Memory (physical + swap file) available.
>> persist_test
>> memory
Maximum possible array:        2183 MB (2.289e+09 bytes) *
Memory available for all arrays:        2183 MB (2.289e+09 bytes) *
Memory used by MATLAB:        1115 MB (1.169e+09 bytes)
Physical Memory (RAM):        8056 MB (8.447e+09 bytes)

*  Limited by System Memory (physical + swap file) available.
>> [~,mexnames] = inmem

mexnames = 

    'winqueryreg'
    'persist_test'

>> clear persist_test
>> [~,mexnames] = inmem

mexnames = 

    'winqueryreg'

>> memory
Maximum possible array:        2174 MB (2.279e+09 bytes) *
Memory available for all arrays:        2174 MB (2.279e+09 bytes) *
Memory used by MATLAB:        1103 MB (1.157e+09 bytes)
Physical Memory (RAM):        8056 MB (8.447e+09 bytes)

*  Limited by System Memory (physical + swap file) available.
>> 
>> % Do it again
>> 
>> persist_test
>> memory
Maximum possible array:        2053 MB (2.153e+09 bytes) *
Memory available for all arrays:        2053 MB (2.153e+09 bytes) *
Memory used by MATLAB:        1206 MB (1.265e+09 bytes)
Physical Memory (RAM):        8056 MB (8.447e+09 bytes)

*  Limited by System Memory (physical + swap file) available.
>> [~,mexnames] = inmem

mexnames = 

    'winqueryreg'
    'persist_test'

>> clear persist_test
>> [~,mexnames] = inmem

mexnames = 

    'winqueryreg'

>> memory
Maximum possible array:        2073 MB (2.174e+09 bytes) *
Memory available for all arrays:        2073 MB (2.174e+09 bytes) *
Memory used by MATLAB:        1202 MB (1.260e+09 bytes)
Physical Memory (RAM):        8056 MB (8.447e+09 bytes)

*  Limited by System Memory (physical + swap file) available.
>> 
James Tursa
  • 2,242
  • 8
  • 9