time needed to access struct vs. "loose" variable - matlab

I have a question regarding the timing when accessing / reassigning variables either from a matlab struct or a matlab variable (any array):
Imagine the scenario that you have one function that creates ten variables (arrays of different dimensions and sizes). This function is getting called within another function that will need these variables produced.
Now, because getting ten variables from a function looks messy, I thought about storing these ten variables in a struct instead, and change my initial function so that it outputs only one struct (with ten fields) instead of ten variables.
Because timing is crucial for me (it's code for an EEG experiment), I wanted to make sure, that the struct approach is not slower, so I wrote the following test function.
function test_timingStructs
%% define struct
strct.a=1; strct.b=2; strct.c=3;
%% define "loose" variables
a = 1; b = 2; c = 3;
%% How many runs?
runs = 1000;
%% time access to struct
x = []; % empty variable
tic
for i=1:runs
x = strct.a; x = strct.b; x = strct.c;
end
t_struct = toc;
%% time access to "loose variables"
x = []; % empty variable
tic
for i=1:runs
x = a; x = b; x = c;
end
t_loose = toc;
%% Plot results
close all; figure;
bar(cat(2,t_struct,t_loose));
set(gca,'xticklabel', {'struct', 'loose variable'})
xlabel('variable type accessed', 'fontsize', 12)
ylabel(sprintf('time (ms) needed for %d accesses to 3 different variables', runs), 'fontsize', 12)
title('Access timing: struct vs "loose" variables', 'fontsize', 15)
end
According to the results, accessing a structure to get the values of a field is considerably slower than just accessing a variable. Can I make this assumption based on the test that I have done?
Is there another way to neatly "package" the ten variables without losing time when I want to access them?

In theory, yes, the access to data within a struct is going to be slower than access to data stored in a variable. This is just the overhead that the higher level datatype incurs.
BUT
In your tests, you are only measuring the access time of the data within the two data structures. When you are using variables, simply assigning one variable to another takes very little time because MATLAB uses copy-on-write and does not actually make a copy of the data in memory until it is modified.
As a result, the test that you have written isn't very useful for determining the actual cost of using a struct because I'm sure your function does something with the data that it receives. As soon as you modify the data, MATLAB will make a copy of the data and peform the requested operation. So to determine what the performance penalty of a struct is, you should time your actual function rather than the no-op function that you're using.
A slightly more realistic test
I have written a test below which compares the struct and variable access where the called function does and does not modify the data.
function timeaccess
sz = round(linspace(1, 200, 100));
[times1, times2, times3, times4] = deal(zeros(size(sz)));
for k = 1:numel(sz)
n = sz(k);
S = struct('a', rand(n), 'b', rand(n), 'c', rand(n));
times1(k) = timeit(#()access_struct(S));
S = struct('a', rand(n), 'b', rand(n), 'c', rand(n));
times2(k) = timeit(#()access_struct2(S));
a = rand(n); b = rand(n); c = rand(n);
times3(k) = timeit(#()access_vars(a, b, c));
a = rand(n); b = rand(n); c = rand(n);
times4(k) = timeit(#()access_vars2(a, b, c));
end
figure
hax1 = subplot(1,2,1);
ylabel('Execution Time (ms)')
xlabel('Size of Variables');
hold on
plot(sz, times2 * 1000, 'DisplayName', 'Struct w/o modification')
plot(sz, times4 * 1000, 'DisplayName', 'Variables w/o modification')
legend(findall(hax1, 'type', 'line'))
hax2 = subplot(1,2,2);
ylabel('Execution Time (ms)')
xlabel('Size of Variables');
hold on
plot(sz, times1 * 1000, 'DisplayName', 'Struct w modification')
plot(sz, times3 * 1000, 'DisplayName', 'Variables w modification')
legend(findall(hax2, 'type', 'line'))
saveas(gcf, 'data_manipulation.png')
legend()
end
function [a, b, c] = access_struct(S)
a = S.a + 1;
b = S.b + 2;
c = S.c + 3;
end
function [a, b, c] = access_struct2(S)
a = S.a;
b = S.b;
c = S.c;
end
function [d, e, f] = access_vars(a, b, c)
d = a + 1;
e = b + 1;
f = c + 1;
end
function [d, e, f] = access_vars2(a, b, c)
d = a;
e = b;
f = c;
end
The results
As you can see, the struct is slower for just assigning a variable to another variable, but as soon as I perform an operation (here I have the very simple operation of adding a constant to each variable), the effect of the access time is negligible.
Summary
Based on the test above, I would assume that the time difference between the two is going to be negligible for your use case. Even if the struct is a little slower, it may be a cleaner design and result in more readable / maintainable code and may be worth the difference in performance.
If you're very concerned about performance, it may be worth looking into a C/C++ mex function to do some of the heavy lifting for you or switch to a more performant language than MATLAB.

Related

Body of this Matlab function works, but not the function itself (interp1 error)

I've written the following piece of subcode (with parameters commented) for an Euler policy iteration algorithm. When I try to run the body of the function (everything below global) for say, a1 = 1, it works, and returns a scalar. However, when I call the function as euler_diff_test(1), I get an error. (Pasted below)
function diff = euler_diff_test(a1)
%the following comments are example parameters. They are in the global line originally.
% r = 0.2, a = 0.5, y = 1.1, a_grid = linspace(0.5,7,100)
%policy_guess = zeros(2,N);
%policy_guess(1,:) = 0.3*a_grid;
%policy_guess(2,:) = 0.3*a_grid;
% M = zeros(2,2); %M for markov transition kernel
% M(1,1) = p;
% M(2,2) = p;
% M(2,1) = 1-p;
% M(1,2) = 1-p;
% j = 1
global r a y a_grid policy_guess M j;
c = (1+r)*a + y - a1; %consumption formula
if c<=1e-02 %don't care about consumption being negative
diff = 888888888888888888888;
else
policy_func = interp1(a_grid', policy_guess', a1, 'linear');
diff = 1/c - beta*(1+r)*(1 ./ policy_func)*M(j,:)';
end
end
Error Reads:
Any help is much appreciated!
The problem is that you dont understand globals nor how they work!
You seem to be doing something like:
N=100; p=0.1;
r = 0.2, a = 0.5, y = 1.1, a_grid = linspace(0.5,7,100)
policy_guess = zeros(2,N);
policy_guess(1,:) = 0.3*a_grid;
policy_guess(2,:) = 0.3*a_grid;
M = zeros(2,2); %M for markov transition kernel
M(1,1) = p;
M(2,2) = p;
M(2,1) = 1-p;
M(1,2) = 1-p;
euler_diff_test(1)
And this is causing the error you show. Of course it is!
First, you need to learn what a global is and what worskpaces are. Each fucntion has its own worskpace or "scope". That means that only variables defined within the workspace are visible by the function itself.
A global variable is one that exist for all workspaces, and everyone can modify it. You seem to want all those variables defined outside the function, inside your function. But realise! when the variables are defined, they are not global. The function starts, and in its first line, it does only know about the existence of a1. Then, later, you define a bunch of variables as global, that the function did not know about. So what does the function do? just create them empty, for you.
If you want your the variables that you create in the main script scope to be global, you need to declare them as global then, not inside the function. So cut your line global ... from the fucntion, and put it on top of the script where you declare all your variables, i.e. on top of
% here!
N=100; p=0.1;
...
in my example.
Now, the important stuff: Global variables are bad. When you have globals, you don't know who modifies, and its super easy to lost track of what is happening to them, because every function that uses a variable a will modify the global a, so its a pain to debug. Almost no one uses globals because of this. The best way is to pass them to the function as input, i.e. define your function as:
function diff = euler_diff_test(a1,r, a, y, a_grid, policy_guess, M, j)

Fast way to get mean values of rows accordingly to subscripts

I have a data, which may be simulated in the following way:
N = 10^6;%10^8;
K = 10^4;%10^6;
subs = randi([1 K],N,1);
M = [randn(N,5) subs];
M(M<-1.2) = nan;
In other words, it is a matrix, where the last row is subscripts.
Now I want to calculate nanmean() for each subscript. Also I want to save number of rows for each subscript. I have a 'dummy' code for this:
uniqueSubs = unique(M(:,6));
avM = nan(numel(uniqueSubs),6);
for iSub = 1:numel(uniqueSubs)
tmpM = M(M(:,6)==uniqueSubs(iSub),1:5);
avM(iSub,:) = [nanmean(tmpM,1) size(tmpM,1)];
end
The problem is, that it is too slow. I want it to work for N = 10^8 and K = 10^6 (see commented part in the definition of these variables.
How can I find the mean of the data in a faster way?
This sounds like a perfect job for findgroups and splitapply.
% Find groups in the final column
G = findgroups(M(:,6));
% function to apply per group
fcn = #(group) [mean(group, 1, 'omitnan'), size(group, 1)];
% Use splitapply to apply fcn to each group in M(:,1:5)
result = splitapply(fcn, M(:, 1:5), G);
% Check
assert(isequaln(result, avM));
M = sortrows(M,6); % sort the data per subscript
IDX = diff(M(:,6)); % find where the subscript changes
tmp = find(IDX);
tmp = [0 ;tmp;size(M,1)]; % add start and end of data
for iSub= 2:numel(tmp)
% Calculate the mean over just a single subscript, store in iSub-1
avM2(iSub-1,:) = [nanmean(M(tmp(iSub-1)+1:tmp(iSub),1:5),1) tmp(iSub)-tmp(iSub-1)];tmp(iSub-1)];
end
This is some 60 times faster than your original code on my computer. The speed-up mainly comes from presorting the data and then finding all locations where the subscript changes. That way you do not have to traverse the full array each time to find the correct subscripts, but rather you only check what's necessary each iteration. You thus calculate the mean over ~100 rows, instead of first having to check in 1,000,000 rows whether each row is needed that iteration or not.
Thus: in the original you check numel(uniqueSubs), 10,000 in this case, whether all N, 1,000,000 here, numbers belong to a certain category, which results in 10^12 checks. The proposed code sorts the rows (sorting is NlogN, thus 6,000,000 here), and then loop once over the full array without additional checks.
For completion, here is the original code, along with my version, and it shows the two are the same:
N = 10^6;%10^8;
K = 10^4;%10^6;
subs = randi([1 K],N,1);
M = [randn(N,5) subs];
M(M<-1.2) = nan;
uniqueSubs = unique(M(:,6));
%% zlon's original code
avM = nan(numel(uniqueSubs),7); % add the subscript for comparison later
tic
uniqueSubs = unique(M(:,6));
for iSub = 1:numel(uniqueSubs)
tmpM = M(M(:,6)==uniqueSubs(iSub),1:5);
avM(iSub,:) = [nanmean(tmpM,1) size(tmpM,1) uniqueSubs(iSub)];
end
toc
%%%%% End of zlon's code
avM = sortrows(avM,7); % Sort for comparison
%% Start of Adriaan's code
avM2 = nan(numel(uniqueSubs),6);
tic
M = sortrows(M,6);
IDX = diff(M(:,6));
tmp = find(IDX);
tmp = [0 ;tmp;size(M,1)];
for iSub = 2:numel(tmp)
avM2(iSub-1,:) = [nanmean(M(tmp(iSub-1)+1:tmp(iSub),1:5),1) tmp(iSub)-tmp(iSub-1)];
end
toc %tic/toc should not be used for accurate timing, this is just for order of magnitude
%%%% End of Adriaan's code
all(avM(:,1:6) == avM2) % Do the comparison
% End of script
% Output
Elapsed time is 58.561347 seconds.
Elapsed time is 0.843124 seconds. % ~70 times faster
ans =
1×6 logical array
1 1 1 1 1 1 % i.e. the matrices are equal to one another

Concatenating structure elements

For example
test = struct('one', [1;2;3], 'two', [4;5;6]);
I would like to vertically concatenate the vectors in the struct test. For example, if it were defined as a cell array instead test = {[1;2;3], [4;5;6]}, I could do vertcat(test{:}). However, vertcat(test{:}) returns a struct object if test is a struct.
I would like to have a solution that does not involve creating a temporary cell array using struct2cell.
What you want to do is to actually use struct2array and then flatten the result.
A = reshape(struct2array(test), [], 1);
% 1
% 2
% 3
% 4
% 5
% 6
Benchmark
As a follow-up, I have performed a little bit of a benchmark comparing the usage of struct2cell to struct2array. We would expect the cell2mat(struct2cell()) method to be slower because 1) it is operating on cell arrays and 2) it uses cell arrays rather than numerical arrays which are notoriously slow. Here is the script I used to perform the tests.
function tests()
sizes = round(linspace(100, 100000));
times1 = zeros(size(sizes));
times2 = zeros(size(sizes));
for k = 1:numel(sizes)
sz = sizes(k);
S = struct('one', rand(sz, 1), 'two', rand(sz, 1));
times1(k) = timeit(#()cellbased(S));
times2(k) = timeit(#()arraybased(S));
end
figure;
plot(sizes, cat(1, times1 * 1000, times2 * 1000));
legend('struct2cell', 'struct2array')
xlabel('Number of elements in S.a and S.b')
ylabel('Execution time (ms)')
end
function C = cellbased(S)
C = cell2mat(struct2cell(S));
end
function C = arraybased(S)
C = reshape(struct2array(S), [], 1);
end
And the results (R2015b)
In my case I managed to do it using:
cell2mat(struct2cell(test))

MATLAB: Using a for loop within another function

I am trying to concatenate several structs. What I take from each struct depends on a function that requires a for loop. Here is my simplified array:
t = 1;
for t = 1:5 %this isn't the for loop I am asking about
a(t).data = t^2; %it just creates a simple struct with 5 data entries
end
Here I am doing concatenation manually:
A = [a(1:2).data a(1:3).data a(1:4).data a(1:5).data] %concatenation function
As you can see, the range (1:2), (1:3), (1:4), and (1:5) can be looped, which I attempt to do like this:
t = 2;
A = [for t = 2:5
a(1:t).data
end]
This results in an error "Illegal use of reserved keyword "for"."
How can I do a for loop within the concatenate function? Can I do loops within other functions in Matlab? Is there another way to do it, other than copy/pasting the line and changing 1 number manually?
You were close to getting it right! This will do what you want.
A = []; %% note: no need to initialize t, the for-loop takes care of that
for t = 2:5
A = [A a(1:t).data]
end
This seems strange though...you are concatenating the same elements over and over...in this example, you get the result:
A =
1 4 1 4 9 1 4 9 16 1 4 9 16 25
If what you really need is just the .data elements concatenated into a single array, then that is very simple:
A = [a.data]
A couple of notes about this: why are the brackets necessary? Because the expressions
a.data, a(1:t).data
don't return all the numbers in a single array, like many functions do. They return a separate answer for each element of the structure array. You can test this like so:
>> [b,c,d,e,f] = a.data
b =
1
c =
4
d =
9
e =
16
f =
25
Five different answers there. But MATLAB gives you a cheat -- the square brackets! Put an expression like a.data inside square brackets, and all of a sudden those separate answers are compressed into a single array. It's magic!
Another note: for very large arrays, the for-loop version here will be very slow. It would be better to allocate the memory for A ahead of time. In the for-loop here, MATLAB is dynamically resizing the array each time through, and that can be very slow if your for-loop has 1 million iterations. If it's less than 1000 or so, you won't notice it at all.
Finally, the reason that HBHB could not run your struct creating code at the top is that it doesn't work unless a is already defined in your workspace. If you initialize a like this:
%% t = 1; %% by the way, you don't need this, the t value is overwritten by the loop below
a = []; %% always initialize!
for t = 1:5 %this isn't the for loop I am asking about
a(t).data = t^2; %it just creates a simple struct with 5 data entries
end
then it runs for anyone the first time.
As an appendix to gariepy's answer:
The matrix concatenation
A = [A k];
as a way of appending to it is actually pretty slow. You end up reassigning N elements every time you concatenate to an N size vector. If all you're doing is adding elements to the end of it, it is better to use the following syntax
A(end+1) = k;
In MATLAB this is optimized such that on average you only need to reassign about 80% of the elements in a matrix. This might not seam much, but for 10k elements this adds up to ~ an order of magnitude of difference in time (at least for me).
Bare in mind that this works only in MATLAB 2012b and higher as described in this thead: Octave/Matlab: Adding new elements to a vector
This is the code I used. tic/toc syntax is not the most accurate method for profiling in MATLAB, but it illustrates the point.
close all; clear all; clc;
t_cnc = []; t_app = [];
N = 1000;
for n = 1:N;
% Concatenate
tic;
A = [];
for k = 1:n;
A = [A k];
end
t_cnc(end+1) = toc;
% Append
tic;
A = [];
for k = 1:n;
A(end+1) = k;
end
t_app(end+1) = toc;
end
t_cnc = t_cnc*1000; t_app = t_app*1000; % Convert to ms
% Fit a straight line on a log scale
P1 = polyfit(log(1:N),log(t_cnc),1); P_cnc = #(x) exp(P1(2)).*x.^P1(1);
P2 = polyfit(log(1:N),log(t_app),1); P_app = #(x) exp(P2(2)).*x.^P2(1);
% Plot and save
loglog(1:N,t_cnc,'.',1:N,P_cnc(1:N),'k--',...
1:N,t_app,'.',1:N,P_app(1:N),'k--');
grid on;
xlabel('log(N)');
ylabel('log(Elapsed time / ms)');
title('Concatenate vs. Append in MATLAB 2014b');
legend('A = [A k]',['O(N^{',num2str(P1(1)),'})'],...
'A(end+1) = k',['O(N^{',num2str(P2(1)),'})'],...
'Location','northwest');
saveas(gcf,'Cnc_vs_App_test.png');

Vectorization of array comparison

I have surjective functions created by matching one element in an array MatchesX.trainIdx to one or more elements in a second array MatchesX.queryIdx.
To obtain only the bijective elements of said funciton I run the same function forward
Matches1=Matcher.match(Descriptors1,Descriptors2);
and then backwards
Matches2=Matcher.match(Descriptors2,Descriptors1);
and then look for the elements occuring in both function in following fashion:
k=1;
DoubleMatches=Matches1;
for i=1:length(Matches1)
for j=1:length(Matches2)
if((Matches1(i).queryIdx==Matches2(j).trainIdx)&&(Matches1(i).trainIdx==Matches2(j).queryIdx))
DoubleMatches(k)=Matches1(i);
k=k+1;
end
end
end
DoubleMatches(k:end)=[];
This of course does the work, but it is rather unelegant and seems to bother the JIT accelerator (calc time with accel on and accel off is the same).
Can you think of a way to vectorize this expresion? Is there any other way of avoiding the JIT from "striking"?
Thanks a lot and sorry about the strange structs, I'm working with MEX-functions. Let me know if rewriting the code in "normal" arrays would help
Access to data in multi-dimensional structures is notoriously slow in MATLAB, so transforming your data to an ordinary array will certainly help:
kk = 1;
DoubleMatches = Matches1;
%// transform to regular array
Matches1queryIdx = [Matches1.queryIdx];
Matches1trainIdx = [Matches1.trainIdx];
Matches2queryIdx = [Matches2.queryIdx];
Matches2trainIdx = [Matches2.trainIdx];
%// loop through transformed data instead of structures
for ii = 1:length(Matches1queryIdx)
for jj = 1:length(Matches1queryIdx)
if((Matches1queryIdx(ii)==Matches2trainIdx(jj)) && ...
(Matches1trainIdx(ii)==Matches2queryIdx(jj)))
DoubleMatches(kk) = Matches1(ii);
kk = kk+1;
end
end
end
DoubleMatches(kk:end)=[];
There is also a solution that is almost entirely vectorized:
matches = sum(...
bsxfun(#eq, [Matches1.queryIdx], [Matches2.trainIdx].') & ...
bsxfun(#eq, [Matches1.trainIdx], [Matches2.queryIdx].'));
contents = arrayfun(#(x)..
repmat(Matches1(x),1,matches(x)), 1:numel(matches), ...
'Uniformoutput', false);
DoubleMatches2 = [contents{:}]';
Note that this can be a lot more memory intensive (it has O(N²) peak memory footprint, as opposed to O(N) for the others, although the data type at peak memory is logical and thus 8x smaller than double...). Better do some checks beforehand which one you should use.
A little test. I used the following dummy data:
Matches1 = struct(...
'queryIdx', num2cell(randi(25,1000,1)),...
'trainIdx', num2cell(randi(25,1000,1))...
);
Matches2 = struct(...
'queryIdx', num2cell(randi(25,1000,1)),...
'trainIdx', num2cell(randi(25,1000,1))...
);
and the following test:
%// Your original method
tic
kk = 1;
DoubleMatches = Matches1;
for ii = 1:length(Matches1)
for jj = 1:length(Matches2)
if((Matches1(ii).queryIdx==Matches2(jj).trainIdx) && ...
(Matches1(ii).trainIdx==Matches2(jj).queryIdx))
DoubleMatches(kk) = Matches1(ii);
kk = kk+1;
end
end
end
DoubleMatches(kk:end)=[];
toc
DoubleMatches1 = DoubleMatches;
%// Method with data transformed into regular array
tic
kk = 1;
DoubleMatches = Matches1;
Matches1queryIdx = [Matches1.queryIdx];
Matches1trainIdx = [Matches1.trainIdx];
Matches2queryIdx = [Matches2.queryIdx];
Matches2trainIdx = [Matches2.trainIdx];
for ii = 1:length(Matches1queryIdx)
for jj = 1:length(Matches1queryIdx)
if((Matches1queryIdx(ii)==Matches2trainIdx(jj)) && ...
(Matches1trainIdx(ii)==Matches2queryIdx(jj)))
DoubleMatches(kk) = Matches1(ii);
kk = kk+1;
end
end
end
DoubleMatches(kk:end)=[];
toc
DoubleMatches2 = DoubleMatches;
% // Vectorized method
tic
matches = sum(...
bsxfun(#eq, [Matches1.queryIdx], [Matches2.trainIdx].') & ...
bsxfun(#eq, [Matches1.trainIdx], [Matches2.queryIdx].'));
contents = arrayfun(#(x)repmat(Matches1(x),1,matches(x)), 1:numel(matches), 'Uniformoutput', false);
DoubleMatches3 = [contents{:}]';
toc
%// Check if all are equal
isequal(DoubleMatches1,DoubleMatches2, DoubleMatches3)
Results:
Elapsed time is 6.350679 seconds. %// ( 1×) original method
Elapsed time is 0.636479 seconds. %// (~10×) method with regular array
Elapsed time is 0.165935 seconds. %// (~40×) vectorized
ans =
1 %// indeed, outcomes are equal
Assuming Matcher.match returns array of the same objects as passed to it as arguments you can solve this like this
% m1 are all d1s which have relation to d2
m1 = Matcher.match(d1,d2);
% m2 are all d2s, which have relation to m1
% and all m1 already have backward relation
m2 = Matcher.match(d2,m1);