4.3. Improving the multiplexer (demonstrates avoiding unneccessary computations with conditional input connectors)¶
The multiplexer from the previous tutorial can cause unnecessary computations in certain constellations. This tutorial shows how to avoid these computations by specifying conditions for the propagation of value changes of the input connector.
4.3.1. Situations, in which unnecessary computations occur¶
If any input of the simple multiplexer from the previous tutorial receives value update, this update will be propagated down the processing chain. In case, the updated input is not currently selected, the output of the multiplexer will produce the same value as before the value update, which causes an unnecessary recomputation of the processing chain.
In order to avoid these unnecessary computations, a means to interrupt the processing of the chain is required. The (multi-) input connectors have a feature to specify conditions on the propagation of value changes, which can be used for this purpose.
4.3.2. Conditions for the input connectors¶
Inputs and multi-inputs have two decorators for methods, which specify the conditional propagation of value changes. For a more detailed explanation of the announcement and notification phases of the propagation of value changes, it is recommended read the section about lazy execution.
- The method decorated with
announce_conditionis evaluated to check if the announcement of a value change shall be propagated. If the condition evaluates to
False, processors further down the processing chain will not be informed about the pending value change, which means, that they will not request this value change to be performed. In case all end points, which request this value update, (such as manually called output connectors or non-lazy inputs) are behind the conditional input in the processing chain, this means, that also the connectors, which are before the conditional input, are not executed.
- The method decorated with
notify_conditionis evaluated after executing the input connector to check if the observing output connectors shall be notified about the changed value. If this evaluation yields
False, the pending announcements are canceled, so that downstream connectors do not request an updated value.
4.3.3. Implementing a conditional multi-input connector for the multiplexer¶
Choosing condition for the multiplexer’s input is trivial.
It should simply check, if the changed input is the one, that is currently selected.
The harder choice is to decide whether to use an
announce_condition or a
At first glance, the
announce_condition is tempting, because it also avoids the computations, that produce the input value, which is not selected by the multiplexer.
Sadly, these computations cannot generally be avoided, because it is always possible, that the changed value is selected by the multiplexer at a later point in time.
In this case, the output connector of the multiplexer must have been informed about the pending value change, in order to request that value to be updated.
And this announcement has not been sent, if the
announce_condition evaluated to
Therefore, the multiplexer’s input must specify a
4.3.4. An improved implementation of the multiplexer¶
>>> import connectors
The following implementation of the improved multiplexer is almost identical to the
Multiplexer class from the previous tutorial.
It is only enhanced by the
__input_condition() method, which is decorated to become the
>>> class Multiplexer: ... def __init__(self, selector=None): ... self.__selector = selector ... self.__data = connectors.MultiInputData() ... ... @connectors.Output() ... def output(self): ... if self.__selector in self.__data: ... return self.__data[self.__selector] ... else: ... return None ... ... @connectors.Input("output") ... def select(self, selector): ... self.__selector = selector ... return self ... ... @connectors.MultiInput("output") ... def input(self, data): ... return self.__data.add(data) ... ... @input.remove ... def remove(self, data_id): ... del self.__data[data_id] ... return self ... ... @input.replace ... def replace(self, data_id, data): ... self.__data[data_id] = data ... return data_id ... ... @input.notify_condition ... def __input_condition(self, data_id, value): ... return data_id == self.__selector
In order to test and demonstrate the avoidance of unnecessary computations, the following test class is implemented:
>>> class Tester: ... @connectors.Input(laziness=connectors.Laziness.ON_ANNOUNCE) ... def input(self, value): ... print("Tester received value:", repr(value))
It has a non-lazy input, which requests the updated value as soon as an update is announced. And whenever it receives a new value, it prints a message.
In the following test set up, two
PassThrough instances are connected to the inputs of a multiplexer, while a
Tester instance is connected to its output.
It is now expected, that the tester prints a message, whenever the selected input of the multiplexer changes its value, while it remains silent, when there is a value change in a not-selected input.
>>> source1 = connectors.blocks.PassThrough("value 1") >>> source2 = connectors.blocks.PassThrough("value 2") >>> multiplexer = Multiplexer() >>> tester = Tester() >>> >>> _ = source1.output.connect(multiplexer.input) >>> _ = source2.output.connect(multiplexer.input) >>> _ = multiplexer.output.connect(tester.input)
Of course, selecting an input causes the output to be updated, so a message from the tester is expected.
>>> _ = multiplexer.select(1) Tester received value: 'value 1'
When input 1 is selected, a change of that input’s value shall also trigger a message from the tester.
>>> _ = source1.input("new value 1") Tester received value: 'new value 1'
But since input 2 is not selected, the tester is not invoked when the value of that input is updated.
>>> _ = source2.input("new value 2")