aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store-query-interpreter
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-10-26 20:37:40 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-10-26 20:42:51 +0200
commitc80751c8ec35aab8b3a2bfaf96e4ca82e29815a0 (patch)
tree61e068e0454d3a290dc7bb4ad09e42050b0b2145 /subprojects/store-query-interpreter
parentchore(deps): dedupe Yarn dependencies (diff)
downloadrefinery-c80751c8ec35aab8b3a2bfaf96e4ca82e29815a0.tar.gz
refinery-c80751c8ec35aab8b3a2bfaf96e4ca82e29815a0.tar.zst
refinery-c80751c8ec35aab8b3a2bfaf96e4ca82e29815a0.zip
refactor(interpreter): aggreagator batching
Optimize calls to potentially costly aggregators by only extracting the value from a stateful aggregator when it is needed by subsequent RETE nodes. This optimization only works with timeless evaluation and delete-and-rederive evaluation disabled, i.e., only for queries without any recursion. Potentially, it could also be extended to other mailboxes if needed. We replace the BehaviorChangingMailbox of ColumnAggregatorNode with a DefaultMailbox to force update batching. Batched updates only extract the value from the aggregator when it has been already updated with all received tuples.
Diffstat (limited to 'subprojects/store-query-interpreter')
-rw-r--r--subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/AggregatorBatchingTest.java186
1 files changed, 186 insertions, 0 deletions
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/AggregatorBatchingTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/AggregatorBatchingTest.java
new file mode 100644
index 00000000..d8e06d82
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/AggregatorBatchingTest.java
@@ -0,0 +1,186 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.query.ModelQueryAdapter;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.query.term.StatefulAggregate;
14import tools.refinery.store.query.term.StatefulAggregator;
15import tools.refinery.store.query.term.Variable;
16import tools.refinery.store.query.view.AnySymbolView;
17import tools.refinery.store.query.view.FunctionView;
18import tools.refinery.store.query.view.KeyOnlyView;
19import tools.refinery.store.representation.Symbol;
20import tools.refinery.store.tuple.Tuple;
21
22import java.util.Map;
23import java.util.Optional;
24
25import static org.hamcrest.MatcherAssert.assertThat;
26import static org.hamcrest.Matchers.is;
27import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertNullableResults;
28
29class AggregatorBatchingTest {
30 private static final Symbol<Boolean> person = Symbol.of("Person", 1);
31 private static final Symbol<Integer> values = Symbol.of("values", 2, Integer.class, null);
32 private static final AnySymbolView personView = new KeyOnlyView<>(person);
33 private static final FunctionView<Integer> valuesView = new FunctionView<>(values);
34
35 private final Query<Integer> query = Query.of(Integer.class, (builder, p1, output) -> builder
36 .clause(
37 personView.call(p1),
38 output.assign(valuesView.aggregate(new InstrumentedAggregator(), p1, Variable.of()))
39 ));
40
41 private int extractCount = 0;
42
43 @Test
44 void batchTest() {
45 var model = createModel();
46 var personInterpretation = model.getInterpretation(person);
47 var valuesInterpretation = model.getInterpretation(values);
48 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
49 var resultSet = queryEngine.getResultSet(query);
50
51 assertThat(extractCount, is(1));
52
53 personInterpretation.put(Tuple.of(0), true);
54 personInterpretation.put(Tuple.of(1), true);
55
56 valuesInterpretation.put(Tuple.of(0, 0), 1);
57 valuesInterpretation.put(Tuple.of(0, 1), 2);
58 valuesInterpretation.put(Tuple.of(0, 2), 3);
59 valuesInterpretation.put(Tuple.of(1, 0), 1);
60 valuesInterpretation.put(Tuple.of(1, 1), -1);
61
62 queryEngine.flushChanges();
63
64 assertThat(extractCount, is(5));
65
66 assertNullableResults(Map.of(
67 Tuple.of(0), Optional.of(6),
68 Tuple.of(1), Optional.of(0),
69 Tuple.of(2), Optional.empty()
70 ), resultSet);
71 }
72
73 @Test
74 void separateTest() {
75 var model = createModel();
76 var personInterpretation = model.getInterpretation(person);
77 var valuesInterpretation = model.getInterpretation(values);
78 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
79 var resultSet = queryEngine.getResultSet(query);
80
81 assertThat(extractCount, is(1));
82
83 personInterpretation.put(Tuple.of(0), true);
84 personInterpretation.put(Tuple.of(1), true);
85
86 queryEngine.flushChanges();
87 assertThat(extractCount, is(3));
88
89 valuesInterpretation.put(Tuple.of(0, 0), 1);
90 valuesInterpretation.put(Tuple.of(1, 0), 1);
91
92 queryEngine.flushChanges();
93 assertThat(extractCount, is(5));
94 assertNullableResults(Map.of(
95 Tuple.of(0), Optional.of(1),
96 Tuple.of(1), Optional.of(1),
97 Tuple.of(2), Optional.empty()
98 ), resultSet);
99
100 valuesInterpretation.put(Tuple.of(0, 1), 2);
101 valuesInterpretation.put(Tuple.of(1, 1), -1);
102
103 queryEngine.flushChanges();
104 assertThat(extractCount, is(9));
105 assertNullableResults(Map.of(
106 Tuple.of(0), Optional.of(3),
107 Tuple.of(1), Optional.of(0),
108 Tuple.of(2), Optional.empty()
109 ), resultSet);
110
111 valuesInterpretation.put(Tuple.of(0, 2), 3);
112
113 queryEngine.flushChanges();
114 assertThat(extractCount, is(11));
115 assertNullableResults(Map.of(
116 Tuple.of(0), Optional.of(6),
117 Tuple.of(1), Optional.of(0),
118 Tuple.of(2), Optional.empty()
119 ), resultSet);
120 }
121
122 private Model createModel() {
123 var store = ModelStore.builder()
124 .symbols(person, values)
125 .with(QueryInterpreterAdapter.builder()
126 .query(query))
127 .build();
128 return store.createEmptyModel();
129 }
130
131 class InstrumentedAggregator implements StatefulAggregator<Integer, Integer> {
132 @Override
133 public Class<Integer> getResultType() {
134 return Integer.class;
135 }
136
137 @Override
138 public Class<Integer> getInputType() {
139 return Integer.class;
140 }
141
142 @Override
143 public StatefulAggregate<Integer, Integer> createEmptyAggregate() {
144 return new InstrumentedAggregate();
145 }
146 }
147
148 class InstrumentedAggregate implements StatefulAggregate<Integer, Integer> {
149 private int sum;
150
151 public InstrumentedAggregate() {
152 this(0);
153 }
154
155 private InstrumentedAggregate(int sum) {
156 this.sum = sum;
157 }
158
159
160 @Override
161 public void add(Integer value) {
162 sum += value;
163 }
164
165 @Override
166 public void remove(Integer value) {
167 sum -= value;
168 }
169
170 @Override
171 public Integer getResult() {
172 extractCount++;
173 return sum;
174 }
175
176 @Override
177 public boolean isEmpty() {
178 return sum == 0;
179 }
180
181 @Override
182 public StatefulAggregate<Integer, Integer> deepCopy() {
183 return new InstrumentedAggregate(sum);
184 }
185 }
186}