3

I have a property in one of the classes I created that currently has an unnecessary get function associated with it. I initially made it to do useful things before returning the value, but now I just need the property value, so the get function has been reduced to this:

    function value =  get.error(obj)
        value = obj.error;
    end

Now, seeing as this code is completely redundant, I decided to delete the get function all together, but this leads to an incredible slowdown in one section of my code where the 'error' property is accessed repeatedly.

In this section of code, the profiler doesn't explicitly say that the missing get function is the cause of the problem (it says all of the time is wasted at the 'end' of a 'for' loop), but when I add the functionally useless code back in, the performance problem goes away.

Why would deleting this useless get function slow down my code?

EDIT: I've isolated the problem enough to post some code samples.

Here's a dummy version of the method call that has the problem: enter image description here

Here's the code profile without the useless getter: enter image description here

and with the magical useless getter: enter image description here

Note that the requires for the performance appear to be as follows:

  1. use some property of my 'pool' object in a computation set to any variable. The 'error' property is the one that caught my attention, but the bug happens with all properties in the same situation.

  2. the computation involves anything, even '.* 0' causes this slowdown, but a single term being set is slowdown free (e.g. obj.pools(i).delta = obj.pools(i).error)

EDIT 2:

Here's the full pool class; perhaps it'll help:

classdef pool < handle

properties
    name;
    unit_count;
    activation;
    net_input = 0;
    %all projections now incoming
    projections = [];
    error; % same is dEd net for rbp
    target;
    clamped_error = false;
    delta;
    clamped_activation = 0; %0 no clamp, 1 soft clamp, 2 hard clamp
    copyback = false;
    copy_from;
    type = 'hidden';

    activation_history;
    error_history;

end

methods
    function obj = pool(name,count,type,copy_from)
        obj.name = name;
        assignin('caller', name, obj);
        obj.unit_count = count;
        obj.error = zeros(1,count);
        obj.target = zeros(1,count);
        obj.delta = zeros(1,count);
        obj.activation = zeros(1,count);
        obj.net_input = zeros(1,count);
        obj.activation_history(:,1) = zeros(1,count);
        obj.error_history(:,1) = zeros(1,count);
        if nargin > 2
            obj.type = type;
            if nargin == 4
                obj.clamped_activation = 2;
                obj.activation = ones(1,count)/2;
                obj.copy_from = copy_from;
                obj.copyback = true;
            end
        else
            obj.type = 'hidden';
        end
        switch obj.type
            case 'input'
                obj.clamped_activation = 2;
            case 'output'
                obj.clamped_error = true;
            case 'bias'
                obj.clamped_activation = 2;
                obj.activation = 1;
            case 'hidden'
        end

    end

    function proj = connect(obj,send_pool,proj_var)
        %see if you need a new projection or if the user provided one

        if nargin == 2
            proj = projection(obj, send_pool);
        else
            proj = proj_var;
        end
        obj.projections = [obj.projections struct('from',send_pool,'using',proj)];
    end


    function value =  get.error(obj)
        value = obj.error;
    end

end

end
Andrew Janke
  • 23,508
  • 5
  • 56
  • 85
zergylord
  • 4,368
  • 5
  • 38
  • 60
  • Surprising. Normally getters make field access slower, not faster. Maybe it's affecting JIT optimization. Can you include a client code snippet showing the access, too? Is it just doing `foo.error`? Is that slow loop inside the class or outside? How fast are the calls with and without the getter? (Try benchmarking just property access in a loop.) Any subclasses or superclasses? What type of value is stored in `error`, and what are the field attributes? Can you reproduce it with a minimal class? Or include the full source to your class and that slow loop? – Andrew Janke Aug 02 '11 at 14:36
  • Sorry I didn't include code earlier; at the time the functions were too large to be useful. I've isolated the problem to a smaller fake function and posted the code. – zergylord Aug 02 '11 at 18:03
  • Strange. I have no clues. Maybe it's creating extra "garbage" in the absence of the getter, but I don't know why. What Matlab version, OS, and architecture (x86/x64) are you using? – Andrew Janke Aug 02 '11 at 19:46
  • I'm using Matlab 7.12.0 (R2011a), which I believe is the latest version. I'm running 64 bit Windows 7 x86. What's weird is that is the absence of a getter, it should use a default, so I don't know why there'd be any difference in garbage :(. Maybe the declaration allows for some JIT compilation magic? – zergylord Aug 02 '11 at 19:54
  • What other fields do you have on your pool objects? How much data is in them (as bytes and number of mxarrays)? Any cell arrays in those fields? – Andrew Janke Aug 02 '11 at 21:41
  • I added the classdef to the question; its a decently large class, with the biggest field being 'projections' which stores an array of class handles to projection objects, which in turn are basically matrices of doubles. – zergylord Aug 02 '11 at 21:47
  • @zergylord let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/2080/discussion-between-andrew-janke-and-zergylord) – Andrew Janke Aug 02 '11 at 22:32

1 Answers1

1

This sounds like Matlab JIT or interpreter weirdness.

You may be looking at a Matlab bug related to MCOS memory management, like that discussed in Dataset array indexing is very slow with Statistics Toolbox. That bug looks like it causes spurious deep copying of objects when referencing their fields in some cases. If you have cell arrays or other complex structures in your pool objects, that could cause similar behavior - they get copied in to new variables, and maybe the deallocation cost is showing up at the "end".

I was able to reproduce similar surprising slow results in several Matlab versions. But I wasn't able to show any difference from the presence of a getter.

File fooclass.m.

classdef fooclass
    properties
        error = 42;
        whatever = 42;
        delta = 1;
        bigjunk = num2cell(rand(1000,40));
        %bigjunk = rand(1000,10000);  % non-cell does not cause slowdown
    end

    methods        
%         function out = get.error(obj)
%             out = obj.error;
%         end
    end

end

File looper.m.

classdef looper
    properties
        pools
    end

    methods
        function obj = looper()
            obj.pools = repmat(fooclass(), [1 5]);;
        end

        function weird(obj)
        p = obj.pools;
        n = numel(p);
        for i = 1:n
            dummy = obj.pools(i).error .* 0;
        end
        end
    end
end

To reproduce:

>> lp = looper;
>> tic; for i = 1:100; weird(lp); end; toc
Elapsed time is 0.600428 seconds.

That's on my R2011a (prerelease) on 64-bit Windows 7. That's slow. The time is proportional to the size of bigjunk, and the profiler says it almost all happens on the p = obj.pools line. This suggests that the object reference is causing a copy instead of using the copy-on-write optimization like it's supposed to. Or it's walking the object graph, or something. Something similar could be going on with your code.

Might be best to just chalk it up to interpreter weirdness, leave the getter in there if it makes your code faster, and wait for a fix in the next release. Sounds worth submitting to MathWorks as a bug report.

Community
  • 1
  • 1
Andrew Janke
  • 23,508
  • 5
  • 56
  • 85
  • 1
    Thanks for the answer; your reproduction of the problem helped greatly. It turns out it was a freed memory problem; if you call 'profile -memory on', then the profiler displays memory usage stats. With this on I could see that my useless getter was being nice and freeing memory when necessary, while the default getter wouldn't. I think I'll report this problem to Mathworks, as this behavior seems like a bug. – zergylord Aug 04 '11 at 19:56