From: James Tursa on
It was recently pointed out to me by a user of my mtimesx tool that if you start with a large sparse matrix A and then do the operation 0*A, the result takes just as much space as the original A even though it only needs enough space for one element. I tracked it down to the MATLAB API mxRealloc function, and indeed the behavior changes from R2008a to R2008b. In 2008a (and earlier I presume, although I only tested R2007a) when a large block is reallocated with mxRealloc the large block is freed and a new small block is allocated, thus recovering the extra memory. But for R2008b and beyond, the large block remains allocated even though only 1 element is requested in the mxRealloc call. So I checked the MATLAB intrinsic * operator and it has the same undesirable behavior so it is apparently calling the same (or similar) routine in the background. e.g., try the following:

A = sprand(10000,10000,0.1);
C = cell(1,100);
for k=1:100; C{k} = 0*A; end

For R2008a and below the above code behaves nicely. For each iteration the 0*A operation temporarily produces a large sparse matrix, but once the zeros are cleared out the result is much smaller and you can easily fit 100 of them in the cell array. But for R2008b and beyond all of the 0*A results take large amounts of memory and quickly exhaust the heap.

So bottom line for me is I think I will have to write my own version of mxRealloc for use in mex functions and not rely on mxRealloc to free the unneeded memory. Also, it appears that MATLAB intrinsic functions that rely on realloc behavior like the 0 * sparse operation may not behave nicely and may need workarounds. Maybe a bug report to TMW is in order.

Tested system: 32-bit WinXP PC, R2007a through R2010a

Anyone out there have comments on this?

James Tursa
From: Walter Roberson on
James Tursa wrote:
> I tracked it down to the MATLAB
> API mxRealloc function, and indeed the behavior changes from R2008a to
> R2008b. In 2008a (and earlier I presume, although I only tested R2007a)
> when a large block is reallocated with mxRealloc the large block is
> freed and a new small block is allocated, thus recovering the extra
> memory. But for R2008b and beyond, the large block remains allocated
> even though only 1 element is requested in the mxRealloc call.

I can see why they might have done this: it might be a performance improvement
strategy for typical code.

If one is creating a logically "new" result, then most of the time one would
allocate a new memory region instead of down-sizing an existing memory region.
Down-sizing an existing memory region would be a characteristic of code that
initializes an array and then dynamically grows the array until it was
finished with it, and then looped back again.

If there is reasonable ground to believe that an array is going through a
repeated initialize-and-grow cycle, then if you shrink the memory allocation
down to the minimum, you risk the possibility that the code is going to have
to do a lot of copying around in order to grow the array as the routine
progresses. Any one particular allocation doesn't hint at how an array is
going to grow (and thus hint at how much consecutive memory should be set
aside for it to avoid having to keep moving the data), but re-allocation does
offer the hint, and the strategy that "if it grew this big before, there's a
good chance it will grow about that big again".

It is, of course, a strategy that fails if you are very tight on memory and
the code is doing something unusual that involves the array staying small
while large temporary objects are created and deleted, with the array suddenly
bursting in size after that... or if you are using the same array to hold
different things at different times, with the initial ones small and the later
ones happening to be large and you preallocate the smaller size instead of the
larger...

Soooo,.. once you have adopted the hypothesis that "a variable that has grown
big once is probably going to grow big again", the cases that break the
hypothesis can start to seem pretty unlikely compared to typical coding.
From: Bruno Luong on
"James Tursa" <aclassyguy_with_a_k_not_a_c(a)hotmail.com> wrote in message <hrq3j1$dm$1(a)fred.mathworks.com>...
> In 2008a (and earlier I presume, although I only tested R2007a) when a large block is reallocated with mxRealloc the large block is freed and a new small block is allocated, thus recovering the extra memory. But for R2008b and beyond, the large block remains allocated even though only 1 element is requested in the mxRealloc call.

James, can you explain how you know with certainty the block size hasn't changed after callibng mxRealloc? As I understand, the block size always change, but the returned pointer value can changed or unchanged. The API decides to do whatever the strategy it likes.

If the block *size* hasn't change, then it's a bug. However I would still be prudent before to associate the behavior 0*sparse with this issue, nothing indicates it causes by mxRealloc.

Interesting issue you pick up here.

Bruno
From: James Tursa on
"Bruno Luong" <b.luong(a)fogale.findmycountry> wrote in message <hrr5en$i61$1(a)fred.mathworks.com>...
> "James Tursa" <aclassyguy_with_a_k_not_a_c(a)hotmail.com> wrote in message <hrq3j1$dm$1(a)fred.mathworks.com>...
> > In 2008a (and earlier I presume, although I only tested R2007a) when a large block is reallocated with mxRealloc the large block is freed and a new small block is allocated, thus recovering the extra memory. But for R2008b and beyond, the large block remains allocated even though only 1 element is requested in the mxRealloc call.
>
> James, can you explain how you know with certainty the block size hasn't changed after callibng mxRealloc? As I understand, the block size always change, but the returned pointer value can changed or unchanged. The API decides to do whatever the strategy it likes.

Yes, I thought of that. Just because you get back the same pointer from mxRealloc doesn't mean definitively that the block size hasn't changed. The only way I know for sure, and the reason I make the claim, is that my posted script runs out of memory when it shouldn't. The *logical* block size may have changed, i.e. the valid addressable length of the block in C has changed, but the *physical* block size apparently has not changed and no memory is returned to the heap as a result of the mxRealloc call or the 0 * sparse operation.

> If the block *size* hasn't change, then it's a bug. However I would still be prudent before to associate the behavior 0*sparse with this issue, nothing indicates it causes by mxRealloc.

Just an assumption on my part that the intrinsic * operator is calling the same realloc function in the background that mxRealloc does based on the similar behavior. But of course I really have no proof one way or the other. All I really know for sure is that neither of them works as I expected them to, and they both tie up heap memory needlessly.

> Interesting issue you pick up here.

And annoying. I am going to have to recode my sparse matrix C stuff.

James Tursa
From: Bruno Luong on
"James Tursa" <aclassyguy_with_a_k_not_a_c(a)hotmail.com> wrote in message <hrrbe0$5kt$1(a)fred.mathworks.com>...
> "Bruno Luong" <b.luong(a)fogale.findmycountry> wrote in message <hrr5en$i61$1(a)fred.mathworks.com>...
> > "James Tursa" <aclassyguy_with_a_k_not_a_c(a)hotmail.com> wrote in message <hrq3j1$dm$1(a)fred.mathworks.com>...
> > > In 2008a (and earlier I presume, although I only tested R2007a) when a large block is reallocated with mxRealloc the large block is freed and a new small block is allocated, thus recovering the extra memory. But for R2008b and beyond, the large block remains allocated even though only 1 element is requested in the mxRealloc call.
> >
> > James, can you explain how you know with certainty the block size hasn't changed after callibng mxRealloc? As I understand, the block size always change, but the returned pointer value can changed or unchanged. The API decides to do whatever the strategy it likes.
>
> Yes, I thought of that. Just because you get back the same pointer from mxRealloc doesn't mean definitively that the block size hasn't changed. The only way I know for sure, and the reason I make the claim, is that my posted script runs out of memory when it shouldn't. The *logical* block size may have changed, i.e. the valid addressable length of the block in C has changed, but the *physical* block size apparently has not changed and no memory is returned to the heap as a result of the mxRealloc call or the 0 * sparse operation.

James, I run this code on 2010A/Vista64, and it obviously runs until the end, showing that mxRealloc is functioning. What if you run on your side?

#include "mex.h"

#define MAXTEST 100

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {

void *ptr[MAXTEST];
int n;

/* Allocate/Reallocte MAXTEST times */
for (n=0; n<MAXTEST; n++) {
ptr[n] = mxMalloc(1073741824); /* <- 1 Gbytes */
if (!ptr[n]) break;
mexPrintf("Allocate %d times, ptr[%d]=%p\n", n+1, n, ptr[n]);
/* If the following line is removed, 'Out of memory' error will be issued assuming RAM is less than 100 Bbytes*/
ptr[n] = mxRealloc(ptr[n], 1);

}
for (; n--;) {
if (ptr[n]) mxFree(ptr[n]);
}
return;
}

Bruno