1

Suppose you have a class method in MATLAB that you would like to memoize so that previously computed results are cached, saving computation time. MATLAB has a built-in memoize function, but the documentation focuses solely on applying this function to ordinary MATLAB functions. As of version R2022b, the documentation offers no guidance regarding memoization of class methods.

One approach that works is to take the original public class method, rename it, make it private or protected to hide it from the class's public interface, and create a new method which is just a wrapper that has the original method's name and routes method calls to a memoized version of the hidden original class method.

classdef SomeClass < handle
    methods
        function returnValue = MemoizedVersionOfClassMethod(this, args)
            memoizedMethod = memoize(@NonMemoizedVersionOfClassMethod);
            returnValue = memoizedMethod(this, args);
        end
    end

    methods (Access = protected)
        function returnValue = NonMemoizedVersionOfClassMethod(this, args)
            returnValue = % do some expensive computation here
        end
    end
end

Is there an alternate way to do this that avoids having to create a wrapper method for each memoized class method?

There is an existing (unanswered) question (Memoize a method of a class in matlab) asking how to memoize a class method. My code above provides a solution, but I'm not sure it's a good solution. Can this be done in a way that avoids the proliferation of wrapper methods that accompanies this approach?

Matt
  • 2,339
  • 1
  • 21
  • 37
  • An elegant answer would be a `memoize` property that could be set for a set of methods. (EG: `(Access = public, Memoizable = true)`), which of course doesn't exist. Your current solution actually fairly correct to me. Another option I may take, depending on the problem, would be to memoize the relevant contents of the method. A galaxy-brain solution would be to write a class names `MemoizeWrapper`, which contains a copy of the class to memoize as a private property, and passes the method calls using some [mumble] dynamic method name interpretation. (Likely fun, but terrible idea.) – Pursuit Jan 06 '23 at 21:35

1 Answers1

2

It seems be possible to memoize all of the methods in a class at once, rather than the clunky feel of memoizing them one at a time.

This is done by memoizing the each method the first time it is used and storing the result. These function handles are pulled from storage for future uses. To make the interface work, we need to use Matlab's syntax where a method can be called using a string.

The final calling convention looks like this:

%% For a representaive object
>> m = memoizeTest();

%% We can make a bare call to the method
>> m.WorkingMethod([1 2 3])

%% Or make a call to the memoized method, which serves as the interface
>> m.memoized('WorkingMethod', [1 2 3])

Some concerns I would want to test, depending on use, include:

  • How does this behave if the method calls try to change the state of the object
  • What is the performance hit when using a string for the each method call.

To push this even further, the general purpose memoization can be implemented as a separate class, and then we can use Matlab's multiple inheritance feature to get the same behavior.

Sample code for everything is below.


Sample code #1, adding features to the original class

This code implements memoization of all methods using one property and one method

classdef memoizeTest < handle
    
    methods (Access = public)
        %This is the actual, working method we want to memoize
        function returnValue = WorkingMethod(self, args)
            %Pretend this line is slow :)
            returnValue = sum(args);
        end
    end
    
    
    %%%% General memoization
    %    The code below should memoize ALL methods in the class, at the
    %    cost of a change to the calling syntax. The memoized methods need
    %    to be called using a string.
    properties (Access = private)
        %Add a class property to store our memoized methods
        memoizedMethods = containers.Map;
    end
    
    methods (Access = public)
        %Create a public method "memoized" with the following semantics:
        %
        %For an instance of this class X consider the following cases
        %    y1 = X.MethodName(inputs)
        %    y2 = X.memoized('MethodName', inputs)
        %y1 and y2 will return the same results, within the limits of the
        %"memoize" function.
        
        function out = memoized(self, strMethod, args)
            if  ismember(strMethod, methods(self))
                
                %For the first call with a particular method, create and
                %memoize a function handle view of the method
                if ~self.memoizedMethods.isKey(strMethod)
                    fn_method = @(varargin)self.(strMethod)(varargin{:});
                    fn = memoize(fn_method);
                    self.memoizedMethods(strMethod) = fn;
                end
                
                %For all calls, get the store function handle out of
                %storage, and use it.
                fn = self.memoizedMethods(strMethod);
                out = fn(args);
                
            else
                error(['No appropriate method, property ''' strMethod ''''])
            end
        end
    end
end

Sample code #2, using multiple inheritance

The original code now requires very little adjustment

classdef memoizeTest < handle & Memoizable       
    methods (Access = public)
        %This is the actual, working method we want to memoize
        function returnValue = WorkingMethod(self, args)
            %Pretend this line is slow :)
            returnValue = sum(args);
        end
    end
end

The Memoizable class looks like this

classdef Memoizable < handle
    %%%% General memoization
    %    The code below should memoize ALL methods in the class, at the
    %    cost of a change to the calling syntax. The memoized methods need
    %    to be called using a string.
    properties (Access = private)
        %Add a class property to store our memoized methods
        memoizedMethods = containers.Map;
    end
    
    methods (Access = public)
        %Create a public method "memoized" with the following semantics:
        %
        %For an instance of this class X consider the following cases
        %    y1 = X.MethodName(inputs)
        %    y2 = X.memoized('MethodName', inputs)
        %y1 and y2 will return the same results, within the limits of the
        %"memoize" function.
        
        function out = memoized(self, strMethod, args)
            if  ismember(strMethod, methods(self))
                
                %For the first call with a particular method, create and
                %memoize a function handle view of the method
                if ~self.memoizedMethods.isKey(strMethod)
                    fn_method = @(varargin)self.(strMethod)(varargin{:});
                    fn = memoize(fn_method);
                    self.memoizedMethods(strMethod) = fn;
                end
                
                %For all calls, get the store function handle out of
                %storage, and use it.
                fn = self.memoizedMethods(strMethod);
                out = fn(args);
                
            else
                error(['No method ''' strMethod ''' found'])
            end
        end
    end
end
Pursuit
  • 12,285
  • 1
  • 25
  • 41