4.5. Improving the polynomial implementation (demonstrates memory saving techniques)¶
4.5.1. The problem of caching intermediate values¶
Multiply classes store references to their input parameters.
Also, because these classes are meant to be useful outside the scope of the polynomial computation, the caching of their result value is not disabled.
This leads to all intermediate results of the polynomial to remain in memory, even after the computation of the final result has finished.
Due to the caching of the final result, the intermediate results are no longer needed, once the computation has finished.
4.5.2. Avoiding the caching of intermediate results¶
FourierTransform class in the transfer function tutorial has solved the issue of storing its input value, by deleting it in the getter method.
For classes like this, it would be sufficient to disable the caching of the output value, to avoid that intermediate results are stored.
But this solution is only applicable to classes with no more than one input and one output connector, which is not the case with the
To solve this problem in use cases like this, the Connectors package provides the
WeakrefProxyGenerator class, that stores a strong reference to its input value, propagates a weak reference to it through its output connector.
In order to delete the strong reference, once it is no longer needed, this class also provides an input connector, that deletes the strong reference, once the result of the following processing step has been computed.
In combination with disabling the caching of the output connector, that produced the input value for the
WeakrefProxyGenerator instance, this causes the input value to be garbage collected.
4.5.3. Block diagram of the improved polynomial implementation¶
The block diagram of a polynomial implementation, that uses
WeakrefProxyGenerators, is shown below.
WeakrefProxyGenerators are highlighted in red.
Note the backwards dependencies of the
WeakrefProxyGenerators on the output of processing classes, by which they are followed.
This is a feedback loop to tell the
WeakrefProxyGenerators, that they can delete the strong reference to their input values.
4.5.4. Implementation of the improved polynomial¶
First, the building blocks of the polynomial have to be defined.
They are identical to the ones from the previous tutorial (and they are only shown here, so the implementation of the improved polynomial can be tested with
>>> import numpy >>> import connectors >>> >>> class Power: ... def __init__(self, base=0, exponent=1): ... self.__base = base ... self.__exponent = exponent ... ... @connectors.Output() ... def get_result(self): ... return numpy.power(self.__base, self.__exponent) ... ... @connectors.Input("get_result") ... def set_base(self, base): ... self.__base = base ... ... @connectors.Input("get_result") ... def set_exponent(self, exponent): ... self.__exponent = exponent >>> >>> class Multiply: ... def __init__(self, factor1=0, factor2=0): ... self.__factor1 = factor1 ... self.__factor2 = factor2 ... ... @connectors.Output() ... def get_result(self): ... return numpy.multiply(self.__factor1, self.__factor2) ... ... @connectors.Input("get_result") ... def set_factor1(self, factor): ... self.__factor1 = factor ... ... @connectors.Input("get_result") ... def set_factor2(self, factor): ... self.__factor2 = factor >>> >>> class Sum: ... def __init__(self): ... self.__summands = connectors.MultiInputData() ... ... @connectors.Output() ... def get_result(self): ... return sum(tuple(self.__summands.values())) ... ... @connectors.MultiInput("get_result") ... def add_summand(self, summand): ... return self.__summands.add(summand) ... ... @add_summand.remove ... def remove_summand(self, data_id): ... del self.__summands[data_id]
The implementation of the
Polynomial class is conceptually similar to that from the previous tutorial.
But it contains extra lines of code for disabling the caching of the output connectors and for inserting the
WeakrefProxyGenerator instances in the processing chain.
>>> class Polynomial: ... def __init__(self, coefficients): ... self.__powers =  ... self.__sum = Sum() ... for e, c in enumerate(coefficients): ... power = Power(exponent=e) ... self.__powers.append(power) ... power.get_result.set_caching(False) ... power_weakref = connectors.blocks.WeakrefProxyGenerator().input.connect(power.get_result) ... weighting = Multiply(factor2=c).set_factor1.connect(power_weakref.output) ... weighting.get_result.set_caching(False) ... weighting.get_result.connect(power_weakref.delete_reference) ... weighting_weakref = connectors.blocks.WeakrefProxyGenerator().input.connect(weighting.get_result) ... weighting_weakref.output.connect(self.__sum.add_summand) ... self.__sum.get_result.connect(weighting_weakref.delete_reference) ... ... @connectors.MacroInput() ... def set_variable(self): ... for p in self.__powers: ... yield p.set_base ... ... @connectors.MacroOutput() ... def get_result(self): ... return self.__sum.get_result
4.5.5. Using the implementation of the polynomial¶
The usage of the
Polynomial is identical to that from the previous tutorial.
>>> polynomial = Polynomial(coefficients=(5.0, -3.0, 2.0)) # y = 2*x**2 - 3*x + 5 >>> polynomial.set_variable(4.0).get_result() # compute the polynomial for a scalar 25.0 >>> polynomial.set_variable([-2, -1, 0, 1, 2]).get_result() # compute the polynomial for elements of an array array([19., 10., 5., 4., 7.])