Skip to content
This repository was archived by the owner on Jun 18, 2026. It is now read-only.

Commit 7519426

Browse files
Merge pull request #148 from sauravbhattacharya001/perf/quadtree-sqrt-elimination
perf: eliminate Math.sqrt from force-directed repulsion hot path
2 parents a067ac4 + 56749c6 commit 7519426

2 files changed

Lines changed: 35 additions & 27 deletions

File tree

Gvisual/src/gvisual/ForceDirectedLayout.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,21 +193,25 @@ public ForceDirectedLayout compute() {
193193
if (n > BARNES_HUT_THRESHOLD) {
194194
// Barnes-Hut: O(V log V) approximation via quadtree
195195
QuadTree qt = QuadTree.build(pos, n);
196+
double kSq = k * k;
197+
double thetaSq = BH_THETA * BH_THETA;
196198
for (int i = 0; i < n; i++) {
197-
qt.applyRepulsion(i, pos[i][0], pos[i][1], k, disp[i], BH_THETA);
199+
qt.applyRepulsion(i, pos[i][0], pos[i][1], kSq, disp[i], thetaSq);
198200
}
199201
} else {
200202
// Brute-force: O(V^2) all-pairs (fine for small graphs)
203+
double kSqBF = k * k;
201204
for (int i = 0; i < n; i++) {
202205
for (int j = i + 1; j < n; j++) {
203206
double dx = pos[i][0] - pos[j][0];
204207
double dy = pos[i][1] - pos[j][1];
205-
double dist = Math.sqrt(dx * dx + dy * dy);
206-
if (dist < MIN_DIST) dist = MIN_DIST;
208+
double distSq = dx * dx + dy * dy;
209+
if (distSq < MIN_DIST * MIN_DIST) distSq = MIN_DIST * MIN_DIST;
207210

208-
double force = (k * k) / dist;
209-
double fx = (dx / dist) * force;
210-
double fy = (dy / dist) * force;
211+
// force = k²/dist; fx = dx/dist * force = dx * k²/distSq
212+
double f = kSqBF / distSq;
213+
double fx = dx * f;
214+
double fy = dy * f;
211215

212216
disp[i][0] += fx;
213217
disp[i][1] += fy;

Gvisual/src/gvisual/QuadTree.java

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
final class QuadTree {
2020

2121
private static final double MIN_DIST = 0.01;
22+
private static final double MIN_DIST_SQ = MIN_DIST * MIN_DIST;
2223

2324
private double cx, cy; // center of mass
2425
private int mass; // number of bodies
@@ -108,42 +109,45 @@ private void putInChild(int idx, double px, double py) {
108109
* Computes repulsive force on body {@code i} at (px, py) from this
109110
* quadtree node, accumulating into disp[0] (dx) and disp[1] (dy).
110111
*
111-
* @param i index of the body (skip self)
112-
* @param px x-position of body i
113-
* @param py y-position of body i
114-
* @param k optimal distance constant
115-
* @param disp displacement array to accumulate into [dx, dy]
116-
* @param theta Barnes-Hut opening angle (lower = more accurate)
112+
* @param i index of the body (skip self)
113+
* @param px x-position of body i
114+
* @param py y-position of body i
115+
* @param kSq pre-computed k² (optimal distance squared)
116+
* @param disp displacement array to accumulate into [dx, dy]
117+
* @param thetaSq pre-computed theta² for the Barnes-Hut opening angle
117118
*/
118119
void applyRepulsion(int i, double px, double py,
119-
double k, double[] disp, double theta) {
120+
double kSq, double[] disp, double thetaSq) {
120121
if (mass == 0) return;
121122

122123
double dx = px - cx;
123124
double dy = py - cy;
124125
double distSq = dx * dx + dy * dy;
125-
double dist = Math.sqrt(distSq);
126126

127127
if (mass == 1 && bodyIndex >= 0) {
128128
if (bodyIndex == i) return;
129-
if (dist < MIN_DIST) dist = MIN_DIST;
130-
double force = (k * k) / dist;
131-
disp[0] += (dx / dist) * force;
132-
disp[1] += (dy / dist) * force;
129+
if (distSq < MIN_DIST_SQ) distSq = MIN_DIST_SQ;
130+
// force = k² / dist; fx = (dx/dist)*force = dx * k² / dist²
131+
double f = kSq / distSq;
132+
disp[0] += dx * f;
133+
disp[1] += dy * f;
133134
return;
134135
}
135136

136-
if (size / dist < theta) {
137-
if (dist < MIN_DIST) dist = MIN_DIST;
138-
double force = (k * k) * mass / dist;
139-
disp[0] += (dx / dist) * force;
140-
disp[1] += (dy / dist) * force;
137+
// Barnes-Hut check: size/dist < theta ⟺ size²/distSq < theta²
138+
// Avoids Math.sqrt in the common "far enough" case.
139+
double sizeSq = size * size;
140+
if (sizeSq < thetaSq * distSq) {
141+
if (distSq < MIN_DIST_SQ) distSq = MIN_DIST_SQ;
142+
double f = kSq * mass / distSq;
143+
disp[0] += dx * f;
144+
disp[1] += dy * f;
141145
return;
142146
}
143147

144-
if (nw != null) nw.applyRepulsion(i, px, py, k, disp, theta);
145-
if (ne != null) ne.applyRepulsion(i, px, py, k, disp, theta);
146-
if (sw != null) sw.applyRepulsion(i, px, py, k, disp, theta);
147-
if (se != null) se.applyRepulsion(i, px, py, k, disp, theta);
148+
if (nw != null) nw.applyRepulsion(i, px, py, kSq, disp, thetaSq);
149+
if (ne != null) ne.applyRepulsion(i, px, py, kSq, disp, thetaSq);
150+
if (sw != null) sw.applyRepulsion(i, px, py, kSq, disp, thetaSq);
151+
if (se != null) se.applyRepulsion(i, px, py, kSq, disp, thetaSq);
148152
}
149153
}

0 commit comments

Comments
 (0)