Skip to content

Commit 3286ab7

Browse files
committed
Don't create more objects than actually needed
We re-use PointError objects instead of sending them to GC. This does require external code to re-use the simplifier itself.
1 parent 6499b8b commit 3286ab7

File tree

2 files changed

+95
-26
lines changed

2 files changed

+95
-26
lines changed

libs/geo/src/main/java/org/elasticsearch/geometry/simplify/GeometrySimplifier.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ public abstract class GeometrySimplifier<T extends Geometry> {
2323
protected final int maxPoints;
2424
protected final SimplificationErrorCalculator calculator;
2525
protected final PointError[] points;
26+
protected PointError lastRemoved;
2627
protected final Monitor monitor;
2728
protected int length;
29+
protected int objCount = 0;
2830
protected String description;
2931

3032
protected final PriorityQueue<PointError> queue = new PriorityQueue<>();
@@ -55,7 +57,7 @@ public void reset() {
5557
* Consume a single point on the stream of points to be simplified
5658
*/
5759
public void consume(double x, double y) {
58-
PointError pointError = new PointError(length, x, y);
60+
PointError pointError = makePointErrorFor(length, x, y);
5961
if (length > 1) {
6062
// we need at least three points to calculate the error of the middle point
6163
points[length - 1].error = calculator.calculateError(points[length - 2], points[length - 1], pointError);
@@ -78,8 +80,27 @@ public void consume(double x, double y) {
7880
*/
7981
public abstract T produce();
8082

83+
private PointError makePointErrorFor(int index, double x, double y) {
84+
if (index == maxPoints) {
85+
if (lastRemoved == null) {
86+
this.objCount++;
87+
return new PointError(index, x, y);
88+
} else {
89+
return lastRemoved.reset(index, x, y);
90+
}
91+
} else {
92+
if (points[index] == null) {
93+
this.objCount++;
94+
return new PointError(index, x, y);
95+
} else {
96+
return points[index].reset(index, x, y);
97+
}
98+
}
99+
}
100+
81101
private void removeAndAdd(int toRemove, PointError pointError) {
82102
assert toRemove > 0; // priority queue can never include first point as that always has zero error by definition
103+
this.lastRemoved = this.points[toRemove];
83104
// Shift all points to the right of the removed point over it in the array
84105
System.arraycopy(this.points, toRemove + 1, this.points, toRemove, maxPoints - toRemove - 1);
85106
// Add the new point to the end of the array
@@ -119,8 +140,8 @@ private void updateErrorAt(int index) {
119140
*/
120141
public static class PointError implements SimplificationErrorCalculator.PointLike, Comparable<PointError> {
121142
private int index;
122-
final double x;
123-
final double y;
143+
private double x;
144+
private double y;
124145
double error = 0;
125146

126147
PointError(int index, double x, double y) {
@@ -148,6 +169,13 @@ public double x() {
148169
public double y() {
149170
return y;
150171
}
172+
173+
public PointError reset(int index, double x, double y) {
174+
this.index = index;
175+
this.x = x;
176+
this.y = y;
177+
return this;
178+
}
151179
}
152180

153181
/**

libs/geo/src/test/java/org/elasticsearch/geometry/simplify/GeometrySimplifierTests.java

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import org.elasticsearch.geometry.utils.GeometryValidator;
1818
import org.elasticsearch.geometry.utils.WellKnownText;
1919
import org.elasticsearch.test.ESTestCase;
20+
import org.hamcrest.Matcher;
21+
import org.hamcrest.Matchers;
2022

2123
import java.io.BufferedReader;
2224
import java.io.FileNotFoundException;
@@ -29,6 +31,7 @@
2931
import java.util.HashMap;
3032
import java.util.List;
3133
import java.util.PriorityQueue;
34+
import java.util.function.Function;
3235
import java.util.zip.GZIPInputStream;
3336

3437
import static org.hamcrest.Matchers.closeTo;
@@ -111,36 +114,44 @@ public void endSimplification(String description, List<SimplificationErrorCalcul
111114
simplifications.computeIfPresent(description, (k, v) -> v.asCompleted());
112115
}
113116

114-
public void assertCompleted(int simplificationCount, int minAdded, int minRemoved) {
117+
public void assertCompleted(int simplificationCount, int shouldHaveAdded, int shouldHaveRemoved) {
118+
assertCompleted(simplificationCount, shouldHaveAdded, shouldHaveRemoved, Matchers::equalTo);
119+
}
120+
121+
public void assertCompleted(
122+
int simplificationCount,
123+
int shouldHaveAdded,
124+
int shouldHaveRemoved,
125+
Function<Integer, Matcher<Integer>> matcher
126+
) {
115127
int totalCount = simplifications.values().stream().mapToInt(v -> v.count).sum();
116-
assertThat(
117-
"Expected at least " + simplificationCount + " simplifications",
118-
totalCount,
119-
greaterThanOrEqualTo(simplificationCount)
120-
);
128+
assertThat("Expected " + simplificationCount + " simplifications", totalCount, matcher.apply(simplificationCount));
121129
for (TestSimplificationState state : simplifications.values()) {
122130
assertTrue("Simplification should be completed: " + state.description, state.completed);
123131
}
124132
assertThat(
125-
"Expected at least " + minAdded + " points added when maxPoints was " + maxPoints,
133+
"Expected " + shouldHaveAdded + " points added when maxPoints was " + maxPoints,
126134
added,
127-
greaterThanOrEqualTo(minAdded)
135+
matcher.apply(shouldHaveAdded)
128136
);
129137
assertThat(
130-
"Expected at least " + minRemoved + " points removed when maxPoints was " + maxPoints,
138+
"Expected " + shouldHaveRemoved + " points removed when maxPoints was " + maxPoints,
131139
removed,
132-
greaterThanOrEqualTo(minRemoved)
140+
matcher.apply(shouldHaveRemoved)
133141
);
134142
}
135143
}
136144

137145
public void testShortLine() {
138-
GeometrySimplifier<Line> simplifier = new GeometrySimplifier.LineStrings(10, calculator());
146+
int maxPoints = 10;
147+
var monitor = new TestMonitor("ShortLine", maxPoints);
148+
GeometrySimplifier<Line> simplifier = new GeometrySimplifier.LineStrings(maxPoints, calculator(), monitor);
139149
Line line = new Line(new double[] { -1, 0, 1 }, new double[] { -1, 0, 1 });
140150

141151
// Test full geometry simplification
142152
Line simplified = simplifier.simplify(line);
143153
assertThat("Same line", simplified, equalTo(line));
154+
monitor.assertCompleted(0, 0, 0); // simplification never actually used
144155

145156
// Test streaming simplification
146157
simplifier.reset();
@@ -149,11 +160,14 @@ public void testShortLine() {
149160
}
150161
Line streamSimplified = simplifier.produce();
151162
assertThat("Same line", streamSimplified, equalTo(simplified));
163+
assertThat("Should create exactly the needed number of PointError objects", simplifier.objCount, equalTo(line.length()));
164+
monitor.assertCompleted(0, 3, 0); // stream used for less than maxPoints
152165
}
153166

154167
public void testStraightLine() {
155168
int maxPoints = 10;
156-
GeometrySimplifier<Line> simplifier = new GeometrySimplifier.LineStrings(maxPoints, calculator());
169+
var monitor = new TestMonitor("StraightLine", maxPoints);
170+
GeometrySimplifier<Line> simplifier = new GeometrySimplifier.LineStrings(maxPoints, calculator(), monitor);
157171
double[] x = new double[100];
158172
double[] y = new double[100];
159173
for (int i = 0; i < 100; i++) {
@@ -165,6 +179,7 @@ public void testStraightLine() {
165179
// Test full geometry simplification
166180
Line simplified = simplifier.simplify(line);
167181
assertLineEnds(maxPoints, simplified, line);
182+
monitor.assertCompleted(1, line.length(), line.length() - maxPoints);
168183

169184
// Test streaming simplification
170185
simplifier.reset();
@@ -180,13 +195,16 @@ public void testStraightLine() {
180195
assertTrue(onLine, pointExistsOnLine(px, py, simplified));
181196
assertTrue(onLine, pointExistsOnLine(px, py, streamSimplified));
182197
}
198+
assertThat("Should create exactly the needed number of PointError objects", simplifier.objCount, equalTo(maxPoints + 1));
199+
monitor.assertCompleted(1, 2 * line.length(), 2 * (line.length() - maxPoints));
183200
}
184201

185202
public void testZigZagLine() {
186203
int maxPoints = 10;
187204
int zigSize = 2;
188205
int lineLength = (maxPoints - 1) * zigSize + 1; // chosen to get rid of all y-crossings during simplification
189-
GeometrySimplifier<Line> simplifier = new GeometrySimplifier.LineStrings(maxPoints, calculator());
206+
var monitor = new TestMonitor("ZigZagLine", maxPoints);
207+
GeometrySimplifier<Line> simplifier = new GeometrySimplifier.LineStrings(maxPoints, calculator(), monitor);
190208
double[] x = new double[lineLength];
191209
double[] y = new double[lineLength];
192210
for (int i = 0; i < lineLength; i++) {
@@ -202,6 +220,7 @@ public void testZigZagLine() {
202220
Line simplified = simplifier.simplify(line);
203221
assertLineEnds(maxPoints, simplified, line);
204222
assertLinePointNeverHave(simplified, 0.0);
223+
monitor.assertCompleted(1, line.length(), line.length() - maxPoints);
205224

206225
// Test streaming simplification
207226
simplifier.reset();
@@ -219,6 +238,8 @@ public void testZigZagLine() {
219238
assertTrue(onLine, pointExistsOnLine(px, py, streamSimplified));
220239
}
221240
assertThat("Same line", streamSimplified, equalTo(simplified));
241+
assertThat("Should create exactly the needed number of PointError objects", simplifier.objCount, equalTo(maxPoints + 1));
242+
monitor.assertCompleted(1, 2 * line.length(), 2 * (line.length() - maxPoints));
222243
}
223244

224245
public void testCircularLine() {
@@ -238,14 +259,19 @@ public void testCircularLine() {
238259

239260
// Test streaming simplification
240261
simplifier.reset();
262+
simplifier.notifyMonitorSimplificationStart();
241263
for (int i = 0; i < line.length(); i++) {
242264
simplifier.consume(line.getX(i), line.getY(i));
243265
}
266+
simplifier.notifyMonitorSimplificationEnd();
244267
Line streamSimplified = simplifier.produce();
245268
assertLineEnds(maxPoints, streamSimplified, line);
246269
assertPointsOnCircle(centerX, centerY, radius, streamSimplified);
247270
assertThat("Same line", streamSimplified, equalTo(simplified));
248-
monitor.assertCompleted(1, maxPoints * countFactor, maxPoints * (countFactor - 1));
271+
int shouldHaveAdded = 2 * maxPoints * countFactor;
272+
int shouldHaveRemoved = shouldHaveAdded - maxPoints * 2;
273+
monitor.assertCompleted(2, shouldHaveAdded, shouldHaveRemoved);
274+
assertThat("Should create exactly the needed number of PointError objects", simplifier.objCount, equalTo(maxPoints + 1));
249275
}
250276

251277
public void testCircularPolygon() {
@@ -275,7 +301,10 @@ public void testCircularPolygon() {
275301
assertPolygonEnds("Polygon", maxPoints, simplified, polygon);
276302
assertPointsOnCircle(centerX, centerY, radius, streamSimplified);
277303
assertThat("Same line", streamSimplified, equalTo(simplified));
278-
monitor.assertCompleted(2, maxPoints * countFactor, maxPoints * (countFactor - 1));
304+
int shouldHaveAdded = 2 * maxPoints * countFactor + 2;
305+
int shouldHaveRemoved = shouldHaveAdded - maxPoints * 2;
306+
monitor.assertCompleted(2, shouldHaveAdded, shouldHaveRemoved);
307+
assertThat("Should create exactly the needed number of PointError objects", simplifier.objCount, equalTo(maxPoints + 1));
279308
}
280309

281310
public void testLineWithBackPaths() {
@@ -288,6 +317,7 @@ public void testLineWithBackPaths() {
288317
Line simplified = simplifier.simplify(line);
289318
assertLineEnds(maxPoints, simplified, line);
290319
monitor.assertCompleted(1, x.length, x.length - maxPoints);
320+
assertThat("Should create exactly the needed number of PointError objects", simplifier.objCount, equalTo(maxPoints + 1));
291321
}
292322

293323
public void testLineWithBackPaths2() throws IOException, ParseException {
@@ -297,13 +327,16 @@ public void testLineWithBackPaths2() throws IOException, ParseException {
297327
-0.16 -0.1, -0.17 -0.13, -0.16 -0.16, -0.1 -0.175, 0.008 -0.175, 0.1 -0.15, 0.2 -0.05, 0.3 0.05, 0.4 0.12, 0.5 0.15,
298328
0.67 0.1, 0.8 0.0, 0.7 -0.07, 0.6 -0.12, 0.7 -0.185, 0.85 -0.18, 0.96 -0.09, 1.0 0.0))""";
299329
Line line = (Line) WellKnownText.fromWKT(GeometryValidator.NOOP, true, lineString);
300-
int maxPoints = 15;
301-
var monitor = new TestMonitor("LineWithBackPaths2", maxPoints);
302-
GeometrySimplifier.LineStrings simplifier = new GeometrySimplifier.LineStrings(maxPoints, calculator(), monitor);
303-
simplifier.simplify(line);
304-
simplifier = new GeometrySimplifier.LineStrings(7, calculator(), monitor);
305-
simplifier.simplify(line);
306-
monitor.assertCompleted(2, 2 * line.length(), 2 * (line.length() - maxPoints));
330+
int[] maxPointsArray = new int[] { 15, 7 };
331+
var monitor = new TestMonitor("LineWithBackPaths2", maxPointsArray[0]);
332+
for (int maxPoints : maxPointsArray) {
333+
var simplifier = new GeometrySimplifier.LineStrings(maxPoints, calculator(), monitor);
334+
simplifier.simplify(line);
335+
assertThat("Should create exactly the needed number of PointError objects", simplifier.objCount, equalTo(maxPoints + 1));
336+
}
337+
int shouldHaveAdded = maxPointsArray.length * line.length();
338+
int shouldHaveRemoved = shouldHaveAdded - Arrays.stream(maxPointsArray).sum();
339+
monitor.assertCompleted(2, shouldHaveAdded, shouldHaveRemoved);
307340
}
308341

309342
public void testLineWithNarrowSpikes() {
@@ -328,6 +361,7 @@ public void testLineWithNarrowSpikes() {
328361
Line simplified = simplifier.simplify(line);
329362
assertLineWithNarrowSpikes(simplified, count);
330363
monitor.assertCompleted(1, x.length, x.length - maxPoints);
364+
assertThat("Should create exactly the needed number of PointError objects", simplifier.objCount, equalTo(maxPoints + 1));
331365
}
332366

333367
protected abstract void assertLineWithNarrowSpikes(Line simplified, int spikeCount);
@@ -355,6 +389,7 @@ public void testComplexGeoJsonPolygon() throws IOException, ParseException {
355389
Polygon simplified = simplifier.simplify(polygon);
356390
assertPolygonEnds("Polygon", maxPoints, simplified, polygon);
357391
monitor.assertCompleted(1, polygon.getPolygon().length(), polygon.getPolygon().length() - maxPoints);
392+
assertThat("Should create exactly the needed number of PointError objects", simplifier.objCount, equalTo(maxPoints + 1));
358393
}
359394
}
360395

@@ -370,6 +405,7 @@ public void testComplexGeoJsonPolygons() throws IOException, ParseException {
370405
Polygon simplified = simplifier.simplify(polygon);
371406
assertPolygonEnds("Polygon", maxPoints, simplified, polygon);
372407
monitor.assertCompleted(1, length, length - maxPoints);
408+
assertThat("Should create exactly the needed number of PointError objects", simplifier.objCount, equalTo(maxPoints + 1));
373409
}
374410
}
375411
}
@@ -391,7 +427,12 @@ public void testComplexGeoJsonMultiPolygon() throws IOException, ParseException
391427
int maxPolyPoints = Math.max(4, (int) (simplificationFactor * polygon.getPolygon().length()));
392428
assertPolygonEnds("Polygon[" + i + "]", maxPolyPoints, simplified, polygon);
393429
}
394-
monitor.assertCompleted(simplifiedMultiPolygon.size(), maxPolyLength, maxPolyLength - maxPoints);
430+
monitor.assertCompleted(
431+
simplifiedMultiPolygon.size(),
432+
maxPolyLength,
433+
maxPolyLength - maxPoints,
434+
Matchers::greaterThanOrEqualTo
435+
);
395436
}
396437
}
397438

0 commit comments

Comments
 (0)