Skip to content

Commit 98eefc9

Browse files
committed
perf: optimize plotting pipeline and reduce allocations
- Eliminated cartesianToJavaUnscaled() and fused transform logic into cartesianToJava() → avoids extra method call and Point2D.Double allocation - Mutated input point in-place to reduce heap churn during plotting - Removed getPoint2D() and getPoint3D() helpers to inline projection and transform - Hoisted setProjection(), setStroke(), and FG color setup outside the main loop → avoids redundant calls per data row - Cached fgColor and fgColor2 for reuse in LINES_POINTS mode - Added parallelMaxMin() in Stats to compute all column extrema in one pass → avoids repeated getColumn() calls and redundant loops - PlotData now caches max/min results and invalidates them on setData() - Updated getMin()/getMax() to use cached extrema - Updated describeColumn() to use getMin()/getMax() for consistency and performance Result: reduced per-frame overhead, fewer allocations, and improved responsiveness during zoom/rotate. Profiled using VisualVM
1 parent 573ff36 commit 98eefc9

4 files changed

Lines changed: 65 additions & 47 deletions

File tree

src/main/java/com/babai/ssplot/math/plot/Canvas.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -437,17 +437,14 @@ public void shift(int i, int j) {
437437

438438
// NOTE: these mutate argument (passed point) to avoid allocating new Point2D.Double
439439

440-
/* Transforms from Cartesian space to Java Graphics space. */
441-
/* Takes care of scaling and translation */
440+
/**
441+
* Transforms from Cartesian space to Java Graphics space.
442+
* Takes care of scaling and translation
443+
*/
442444
public Point2D.Double cartesianToJava(Point2D.Double p) {
443-
p.x = scaleFactor*(p.x-zc.x) + zc.x;
444-
p.y = scaleFactor*(p.y-zc.y) + zc.y;
445-
return cartesianToJavaUnscaled(p);
446-
}
447-
448-
private Point2D.Double cartesianToJavaUnscaled(Point2D.Double p) {
449-
p.x = p.x + dx + moveX;
450-
p.y = H - (p.y + dy + moveY);
445+
double sF = 1 - scaleFactor;
446+
p.x = scaleFactor*p.x + sF*zc.x + dx + moveX;
447+
p.y = H - (scaleFactor*p.y + sF*zc.y + dy + moveY);
451448
return p;
452449
}
453450

src/main/java/com/babai/ssplot/math/plot/PlotData.java

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
* including the data, the plot properties and the system
3939
* of equations.
4040
*/
41-
public class PlotData implements Cloneable {
41+
public class PlotData implements Cloneable {
4242
public enum PlotType {
4343
LINES("2D Lines"),
4444
POINTS("2D Points"),
@@ -86,6 +86,9 @@ public enum PointType { SQUARE, CIRCLE };
8686

8787
private Color fgColor, fgColor2;
8888
private String title;
89+
90+
private boolean maxMinComputed = false;
91+
private double[][] maxMin;
8992

9093
public PlotData(double[][] extData) {
9194
data = extData;
@@ -129,7 +132,7 @@ public Optional<String> getAxisLabel(int i) {
129132

130133
// ------------- DATA METHODS -----------------
131134
public double[][] getData() { return data; }
132-
public void setData(double[][] data) { this.data = data; }
135+
public void setData(double[][] data) { maxMinComputed = false; this.data = data; }
133136
public int getRowCount() { return data.length; }
134137

135138
// NOTE: Right now, number of columns of data == number of axes. If that changes
@@ -197,12 +200,13 @@ public void setDataCols(int... dataCols) {
197200

198201
public HashMap<Integer, Integer> getDataColMapping() { return this.dataColumnMapping; }
199202

203+
// NOTE expensive method
200204
public double[] getColumn(int i) {
201205
double[] colData = new double[getRowCount()];
206+
if (i >= getColumnCount()) return colData;
207+
202208
for (int rowIdx = 0; rowIdx < colData.length; rowIdx++) {
203-
if (i < getColumnCount()) {
204-
colData[rowIdx] = this.data[rowIdx][i];
205-
}
209+
colData[rowIdx] = this.data[rowIdx][i];
206210
}
207211
return colData;
208212
}
@@ -211,13 +215,25 @@ public double[] getColumn(int i) {
211215
* @param dataCol index of a column
212216
* @return maximum value among all data in the given column
213217
*/
214-
public double getMax(int dataCol) { return Stats.max(getColumn(dataCol)); }
218+
public double getMax(int dataCol) {
219+
if (!maxMinComputed) {
220+
this.maxMin = Stats.parallelMaxMin(data);
221+
maxMinComputed = true;
222+
}
223+
return this.maxMin[0][dataCol];
224+
}
215225

216226
/**
217227
* @param dataCol index of a column
218228
* @return min value among all data in the given column
219229
*/
220-
public double getMin(int dataCol) { return Stats.min(getColumn(dataCol)); }
230+
public double getMin(int dataCol) {
231+
if (!maxMinComputed) {
232+
this.maxMin = Stats.parallelMaxMin(data);
233+
maxMinComputed = true;
234+
}
235+
return this.maxMin[1][dataCol];
236+
}
221237

222238

223239
// ------------------ NODE METHODS -------------------
@@ -251,9 +267,9 @@ private String formatStats(String colName, int i) {
251267
Q1=%.4f, Median=%.4f, Q3=%.4f
252268
""",
253269
colName,
254-
colData.length,
255-
Stats.min(colData),
256-
Stats.max(colData),
270+
getRowCount(),
271+
getMin(i),
272+
getMax(i),
257273
Stats.mean(colData),
258274
Stats.variance(colData),
259275
Stats.stdDev(colData),

src/main/java/com/babai/ssplot/math/plot/Plotter.java

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,17 @@ private void plotData(PlotData pdata, int lastIndex) {
8080

8181
canv.setFGColor(pdata.getFgColor());
8282
canv.setAxes3d(pdata.getColumnCount() == 3);
83+
canv.setProjection(p);
84+
canv.setStroke(pdata.ptX);
85+
Color fgColor = canv.getFGColor();
86+
Color fgColor2 = pdata.getFgColor2();
8387

88+
double[] row;
89+
90+
// NOTE this is a huge loop, be careful of even small method calls,
91+
// can stockpile easily.
8492
for (int i = 0; i < lastIndex; i++) {
85-
var row = dataset[i];
93+
row = dataset[i];
8694

8795
switch(pdata.getPlotType()) {
8896
case VFIELD -> {
@@ -91,28 +99,25 @@ private void plotData(PlotData pdata, int lastIndex) {
9199
if (row.length >= 4) {
92100
p1 = canv.cartesianToJava(new Point2D.Double(row[0], row[1]));
93101
p2 = canv.cartesianToJava(new Point2D.Double(row[2], row[3]));
94-
95-
canv.drawVector(p1, p2, pdata.getFgColor2());
102+
canv.drawVector(p1, p2, fgColor2);
96103
} else {
97104
System.err.println("Bad vector field data!");
98105
}
99106
}
100107

101108
case POINTS3 -> {
102109
if (row.length >= 3) {
103-
canv.setProjection(p);
104-
canv.drawPoint(getPoint3D(row, dataCols, p, 0, 1, 2), PlotData.PointType.SQUARE, pdata.ptX, pdata.ptY);
110+
p1 = canv.cartesianToJava(p.project(row[dataCols[0]], row[dataCols[1]], row[dataCols[2]]));
111+
canv.drawPoint(p1, PlotData.PointType.SQUARE, pdata.ptX, pdata.ptY);
105112
} else {
106113
System.err.println("Data is not three dimensional!");
107114
}
108115
}
109116

110117
case LINES3 -> {
111118
if (row.length >= 3) {
112-
p2 = getPoint3D(row, dataCols, p, 0, 1, 2);
119+
p2 = canv.cartesianToJava(p.project(row[dataCols[0]], row[dataCols[1]], row[dataCols[2]]));
113120
if (p1 != null) {
114-
canv.setProjection(p);
115-
canv.setStroke(pdata.ptX);
116121
canv.drawLine(p1, p2);
117122
}
118123
p1 = p2;
@@ -122,31 +127,26 @@ private void plotData(PlotData pdata, int lastIndex) {
122127
}
123128

124129
case POINTS, LINES, LINES_POINTS -> {
125-
p2 = getPoint2D(row, dataCols, 0, 1);
130+
p2 = canv.cartesianToJava(new Point2D.Double(row[dataCols[0]], row[dataCols[1]]));
126131

127132
if (p1 != null) {
128133
switch(pdata.getPlotType()) {
129-
case LINES -> {
130-
canv.setStroke(pdata.ptX);
131-
canv.drawLine(p1, p2);
132-
}
134+
case LINES -> canv.drawLine(p1, p2);
133135

134136
case POINTS -> canv.drawPoint(p1, PlotData.PointType.SQUARE, pdata.ptX, pdata.ptY);
135137

136138
case LINES_POINTS -> {
137-
Color c = canv.getFGColor();
138139
Point2D.Double pback = new Point2D.Double(
139140
p1.getX() - (pdata.ptX+4)/2,
140141
p1.getY() - (pdata.ptY+4)/2);
141142
// The line is drawn with plot FGcolor 1
142143
// the points on the top is drawn with plot FGcolor 2
143144
// FIXME generalization needed
144-
canv.setStroke(pdata.ptX);
145145
canv.drawLine(p1, p2);
146146

147-
canv.setFGColor(pdata.getFgColor2());
147+
canv.setFGColor(fgColor2);
148148
canv.drawPoint(pback, PlotData.PointType.CIRCLE, pdata.ptX+4, pdata.ptY+4);
149-
canv.setFGColor(c);
149+
canv.setFGColor(fgColor);
150150
}
151151

152152
default -> {}
@@ -171,16 +171,6 @@ private void plotOthers(PlotData pdata) {
171171
}
172172
}
173173

174-
private Point2D.Double getPoint3D(double[] row, int[] dataCols, Project2D projector,
175-
int i, int j, int k)
176-
{
177-
return canv.cartesianToJava(projector.project(row[dataCols[i]], row[dataCols[j]], row[dataCols[k]]));
178-
}
179-
180-
private Point2D.Double getPoint2D(double[] row, int[] dataCols, int c1, int c2) {
181-
return canv.cartesianToJava(new Point2D.Double(row[dataCols[c1]], row[dataCols[c2]]));
182-
}
183-
184174
public BufferedImage getImage() {
185175
return canv.getImage();
186176
}

src/main/java/com/babai/ssplot/math/plot/Stats.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,19 @@ public static double min(double[] column) {
6969
}
7070
return min;
7171
}
72+
73+
/** Calculate max + mins for all columns at once to avoid multiple loops */
74+
public static double[][] parallelMaxMin(double[][] data) {
75+
int cols = data[0].length;
76+
double[] max = Arrays.copyOf(data[0], cols);
77+
double[] min = Arrays.copyOf(data[0], cols);
78+
79+
for (int i = 1; i < data.length; i++) {
80+
for (int j = 0; j < data[0].length; j++) {
81+
max[j] = Math.max(data[i][j], max[j]);
82+
min[j] = Math.min(data[i][j], min[j]);
83+
}
84+
}
85+
return new double[][] { max, min };
86+
}
7287
}

0 commit comments

Comments
 (0)