# Numerical operations are slow on class properties versus in workspace

18 views (last 30 days)
Daniel Plotnick on 22 Jun 2020
Edited: per isakson on 26 Jun 2020
Hello,
I ran into this behavior I don't really understand: simple numerical operations are order 10x+ slower on class properties than when performed in the workspace.
As an example, I will create a circular index that I want to increment by 1.
N = 2^16; % Number of times to increment
M = 2^8; % What is our wrap point
I = 0; % Initialize our index
t = tic;
clc
disp("Incrementing in workspace");
for n = 1:N
I = mod(I+1,M); % Increment, with wrapping
end
fprintf("%2.0f kOps/second\n",N/toc(t)/1000); % 1000s of operations/second
Great, I am getting ~ 45,000 kOps/second.
Now, instead I will do this all in a class:
classdef speedTestClass < handle
properties
I = 0;
M
end
methods
function increment(obj)
obj.I = mod(obj.I + 1,obj.M); % Increment, with wrapping
end
end
end
And then run it
ob = speedTestClass; % Make the object
ob.M = M; % Set the wrap point
disp("Incrementing in class");
t = tic;
for n = 1:N
ob.increment % Increment, with wrapping
end
fprintf("%2.0f kOps/second\n",N/toc(t)/1000);
Where I get 2400 kOps/second. A slowdown factor of 20x. Yikes.
So, is there a way to more efficiently perform simple operations on class properties? Is there some MEX magic working behind the scenes, and so I would need to MEX my classes?
I couldn't think of a simpler example than this basic index container; I could understand a factor of 2x speed loss, but 20x is huge, and it only gets worse when the computation is more involved and involves multiple methods/properties of the class.
I'll note that on a lark, I actually compiled this into an exe and ran it - the workspace version dropped to the speed of the class based one! Oh no.
Cheers all,
-Dan

Show 1 older comment
Matt J on 23 Jun 2020
simple numerical operations are order 10x+ slower on class properties than when performed in the workspace.
The bottlenecks that you see are not indemic only to property accesses as the modified versions of your tests below will show (in R2019a). They appear to occur in method calls as well, with particularly bad performance for staitc methods.
classdef myclass < handle
properties
I = 0;
M
end
methods
function I=increment(obj,I,M)
I = mod(I + 1,M); % Increment, with wrapping
end
end
methods (Static)
function I=static_increment(I,M)
I = mod(I + 1,M); % Increment, with wrapping
end
end
end
t = tic;
for n = 1:N
I = mod(I+1,M); % Increment, with wrapping
end
fprintf("%2.0f kOps/second\n",N/toc(t)/1000); % 1000s of operations/second
ob = myclass; % Make the object
disp("Incrementing in method call");
t = tic;
for n = 1:N
I=ob.increment(I,M); % Increment, with wrapping
end
fprintf("%2.0f kOps/second\n",N/toc(t)/1000);
disp("Incrementing in property access");
ob.I=I; ob.M=M;
t = tic;
for n = 1:N
ob.I = mod(ob.I+1,ob.M); % Increment, with wrapping
end
fprintf("%2.0f kOps/second\n",N/toc(t)/1000);
disp("Incrementing in static method call");
t = tic;
for n = 1:N
I = myclass.static_increment(I+1,M); % Increment, with wrapping
end
fprintf("%2.0f kOps/second\n",N/toc(t)/1000);
Incrementing in workspace
41935 kOps/second
Incrementing in method call
1746 kOps/second
Incrementing in property access
2758 kOps/second
Incrementing in static method call
8 kOps/second
per isakson on 23 Jun 2020
Daniel Plotnick on 23 Jun 2020
@per isakson - Wow, this is an extremely thorough analysis. I think the gist is
1. Yes, OOP is way slower than functional coding
2. No, I'm not doing something wrong and there is no current way around these speed issues.
3. It is unclear exactly what the bottleneck is, since closed source, but each type of access to a custom class object is going to incur some penalty.
@James Tursa - Thanks for the analysis. I continued working on it, and had planned to post the code/results here, but the Stackoverflow link has a set of benchmark code available that goes way further than I had considered.
I will note, that in the case of the buffers, the fastest solution I found was to only store the pointers and index retrieving methods for the FIFO/LIFO cases in a class; the actual buffer stays inside of the functional workspace. This reduces some of the class-based elegence I had hoped for, but bought me back a considerable speed factor.
pt = myPointer(N);
disp("Using a pointer")
t = tic;
buf = zeros(N,1);
for n = 1:N
buf(pt.v + 1) = n;
pt.increment;
end
fprintf("%2.0f kOps/second\n",N/toc(t)/1000);
with the pointer class
classdef myPointer < handle
properties
v = 0;
n
end
methods
function obj = myPointer(n)
obj.n = n;
end
function increment(obj)
obj.v = mod(obj.v + 1,obj.n);
end
end
end

Matt J on 22 Jun 2020
Edited: Matt J on 22 Jun 2020
It is the repeated M-coded function calls that are slowing you down. Implement the whole loop in a single function call:
function increment(obj,N)
M=obj.M;
I=obj.I;
for n = 1:N
I = mod(I+1,M); % Increment, with wrapping
end
obj.I=I;
end

Show 1 older comment
Matt J on 22 Jun 2020
I understand that your original example was a simplified version, but it doesn't change what the problem is nor the solution that you will need to pursue.You need to re-organize your code so that your loop iterations contain a higher propertion of vectorized commands and a lower proportion of method calls and property accesses. Otherwise, the overhead for the latter will be a bottleneck.
Daniel Plotnick on 23 Jun 2020
Thanks! After your initial answer, I spent some time playing with the internal structure of the class. I intentionally broke out the the increment method from the wrap/mod method. I suffered an additional 2x penalty for going into a seperate method, so it seems to me that I will suffer going into methods the same way I do going into individual m-files.
A lot of this practical excercise is to try to understand my speed penalties when taking advantage of classes and OOP versus straight function based algorithms. There are huge advantages to OOP and design pattern based coding...but at least in Matlab, based on your and Tursa's answers, there will be considerable speed penalties as a consequence of striving for the encapsulation and code maintenance advantages of OOP.
Based on Tursa's comments, it sounds like carefully MEXing the classes won't actually buy me anything. While Matlab is great for prototyping, if I want a fully class based code with high speed, I may need to go for refactoring my code into another language.
I was hoping someone had a workaround; Matlab is my go-to.
Do you know if there is any documentation on the performance penalties with going into m-files? I can't tell if it is memory access (like Tursa suggested would be an issue with MEX), or JIT, or what that causes this speed loss.
Matt J on 23 Jun 2020
See my updated analysis above,
I now think you are seeing overhead from classdef's specifically, though not just from property access. I think the bottom line is one just needs to try to put loops inside functions and not the other way around. and/or to write loops that do lots of computation per iteration (esp. with vectorized commands) instead of really small and quick tasks. These were always best practices in writing efficient Matlab code, although I have to admit, I thought TMW had mananged to better optimize class operations over the years than what we're seeing now.