Saturday, June 13, 2009

Matlab is making me angry!

I love Matlab, but I do not love the following behavior:
>> trx = struct('firstframe',mat2cell((1:200),1,200),'f2i',cell(1,200));
>> for i = 1:length(trx),
trx(i).f2i = @(f) f - trx(i).firstframe + 1;
end
>> tic; clear trx; toc;
Elapsed time is 129.714695 seconds.
Over two minutes? I am now going to be writing a function to go through all my code and replace calls to f2i with the function's definition. It's actually even worse with the real data, for no apparent reason:
>> load(trxfilename);
>> trx

trx =

1x196 struct array with fields:
x
y
theta
a
b
id
moviename
firstframe
arena
nframes
endframe
matname
x_mm
y_mm
a_mm
b_mm
pxpermm
fps

>> for i = 1:length(trx),
trx(i).f2i = @(f) f - trx(i).firstframe + 1;
end
>> tic; clear trx; toc;
Elapsed time is 626.516941 seconds.

3 comments:

Scott Hirsch said...

I can see why this is frustrating. I asked around to see if I could get some insight into what is happening here.

First, a small change that makes a huge difference:

Build the struct in reverse order (i.e., for i=length(trx):-1:1 ). I found the variable constructed this way to clear about 60 times faster.

Here's our guess as to what is happening: each anonymous function holds a pointer to the workspace (containing trx) when it was created, so you are pointing to hundreds of workspaces. There are nasty cycles where each workspace contains trx which contains a reference back to the previous workspace containing trx which contains a reference ...

When MATLAB clears the structure, it has to unwind all of these references carefully. Building in the reverse order keeps these cyclical references lower.

(Of course, this isn't an excuse for the behavior, just our best attempt at an explanation!)

jubjub said...

Hey Scott,

Thanks for clearing this issue up. I can see that this can be slow, particularly if Matlab is storing all of "trx" with the anonymous function, and all of the links to previous "trx" variables. The following code ended up being instantaneous:
>> trx = struct('firstframe',mat2cell((1:200),1,200),'f2i',cell(1,200));
>> for i = 1:length(trx),
firstframe = trx(i).firstframe;
trx(i).f2i = @(f) f - firstframe + 1;
end
>> tic; clear trx; toc;
Elapsed time is 0.002859 seconds.

So, the key seems to be to not reference fields of arrays of structs within anonymous functions, but instead copy them to the workspace. If future versions of Matlab don't have this implemented automatically, I'd say it's a useful piece of documentation. I for one couldn't figure out what was going on -- took me forever to realize that the hold up was in the return calls from functions where "trx" was being deleted by the garbage collector as it went out of scope.

jubjub said...

Looking at:
http://www.mathworks.com/support/solutions/data/1-8PHW5N.html
perhaps it is not enough to just copy "firstframe" to the workspace. It sounds like this will still cause cycles of workspaces to be stored, but apparently fairly light workspaces, as I'm guessing "trx" is not being stored, just firstframe (hence the speedup from storing to firstframe). The issue does not seem to be a problem with arrays of length 200, but more with arrays of length 20000.