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

Commit b9b8b6d

Browse files
refactor: eliminate redundant allocations in RandomWalkAnalyzer
Three performance-impacting cleanup issues fixed: 1. simulateCoverWalk recomputed BFS reachable set (O(V+E)) on every invocation — called 10,000 times per coverTime() call on the same graph. Extracted bfsReachable() helper, called once in coverTime(). 2. Every simulation step allocated a new ArrayList by copying graph.getNeighbors(current). For walks of thousands of steps across 10,000 simulations, this created millions of throwaway lists. - coverTime: uses precomputed neighbor cache (Map<V, List<V>>) - hittingTime/returnTime/walkTrace: uses pickRandom() to iterate directly on the Collection without copying 3. Added pickRandom() and buildNeighborCache() private helpers to consolidate the random-neighbor-selection pattern.
1 parent da1292e commit b9b8b6d

1 file changed

Lines changed: 64 additions & 21 deletions

File tree

Gvisual/src/gvisual/RandomWalkAnalyzer.java

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,16 @@ public <V, E> double commuteDistance(Graph<V, E> graph, V nodeA, V nodeB) {
7373
public <V, E> double coverTime(Graph<V, E> graph, V source) {
7474
validateGraph(graph);
7575
validateNode(graph, source, "source");
76+
// Precompute reachable set once (BFS) — avoids redundant O(V+E)
77+
// traversal on every simulation.
78+
Set<V> reachable = bfsReachable(graph, source);
79+
if (reachable.size() <= 1) return 0;
80+
// Cache neighbor lists to avoid allocating new ArrayLists per step.
81+
Map<V, List<V>> neighborCache = buildNeighborCache(graph, reachable);
7682
long totalSteps = 0;
7783
int maxSteps = graph.getVertexCount() * graph.getVertexCount() * 20;
7884
for (int sim = 0; sim < defaultSimulations; sim++) {
79-
totalSteps += simulateCoverWalk(graph, source, maxSteps);
85+
totalSteps += simulateCoverWalk(source, reachable, neighborCache, maxSteps);
8086
}
8187
return (double) totalSteps / defaultSimulations;
8288
}
@@ -155,9 +161,9 @@ public <V, E> List<V> walkTrace(Graph<V, E> graph, V source, int steps) {
155161
V current = source;
156162
trace.add(current);
157163
for (int i = 0; i < steps; i++) {
158-
List<V> neighbors = new ArrayList<>(graph.getNeighbors(current));
159-
if (neighbors.isEmpty()) break;
160-
current = neighbors.get(rng.nextInt(neighbors.size()));
164+
Collection<V> neighbors = graph.getNeighbors(current);
165+
if (neighbors == null || neighbors.isEmpty()) break;
166+
current = pickRandom(neighbors);
161167
trace.add(current);
162168
}
163169
return trace;
@@ -224,47 +230,84 @@ public WalkSummary(int nc, int ec, Map<V, Double> sd, V mv, double mvp,
224230
private <V, E> int simulateWalkToTarget(Graph<V, E> graph, V source, V target, int maxSteps) {
225231
V current = source;
226232
for (int step = 1; step <= maxSteps; step++) {
227-
List<V> neighbors = new ArrayList<>(graph.getNeighbors(current));
228-
if (neighbors.isEmpty()) return -1;
229-
current = neighbors.get(rng.nextInt(neighbors.size()));
233+
Collection<V> nbrs = graph.getNeighbors(current);
234+
if (nbrs == null || nbrs.isEmpty()) return -1;
235+
// Skip to a random neighbor without copying the full collection.
236+
int idx = rng.nextInt(nbrs.size());
237+
V next = null;
238+
if (nbrs instanceof List) {
239+
next = ((List<V>) nbrs).get(idx);
240+
} else {
241+
for (V v : nbrs) { if (idx-- == 0) { next = v; break; } }
242+
}
243+
current = next;
230244
if (current.equals(target)) return step;
231245
}
232246
return -1;
233247
}
234248

235-
private <V, E> long simulateCoverWalk(Graph<V, E> graph, V source, int maxSteps) {
249+
private <V> long simulateCoverWalk(V source, Set<V> reachable,
250+
Map<V, List<V>> neighborCache, int maxSteps) {
236251
Set<V> visited = new HashSet<>();
237252
V current = source;
238253
visited.add(current);
239-
Set<V> reachable = new HashSet<>();
240-
Queue<V> q = new LinkedList<>();
241-
q.add(source); reachable.add(source);
242-
while (!q.isEmpty()) { V v = q.poll(); for (V n : graph.getNeighbors(v)) if (reachable.add(n)) q.add(n); }
243254
int target = reachable.size();
244-
if (visited.size() >= target) return 0;
245255
for (int step = 1; step <= maxSteps; step++) {
246-
List<V> nb = new ArrayList<>(graph.getNeighbors(current));
247-
if (nb.isEmpty()) return step;
256+
List<V> nb = neighborCache.get(current);
257+
if (nb == null || nb.isEmpty()) return step;
248258
current = nb.get(rng.nextInt(nb.size()));
249259
visited.add(current);
250260
if (visited.size() >= target) return step;
251261
}
252262
return maxSteps;
253263
}
254264

265+
/** BFS to find all vertices reachable from {@code source}. */
266+
private <V, E> Set<V> bfsReachable(Graph<V, E> graph, V source) {
267+
Set<V> reachable = new HashSet<>();
268+
Queue<V> q = new LinkedList<>();
269+
q.add(source);
270+
reachable.add(source);
271+
while (!q.isEmpty()) {
272+
V v = q.poll();
273+
for (V n : graph.getNeighbors(v)) {
274+
if (reachable.add(n)) q.add(n);
275+
}
276+
}
277+
return reachable;
278+
}
279+
280+
/** Cache neighbor lists for a set of vertices — avoids per-step allocation. */
281+
private <V, E> Map<V, List<V>> buildNeighborCache(Graph<V, E> graph, Set<V> vertices) {
282+
Map<V, List<V>> cache = new HashMap<>();
283+
for (V v : vertices) {
284+
Collection<V> neighbors = graph.getNeighbors(v);
285+
cache.put(v, neighbors != null ? new ArrayList<>(neighbors) : Collections.<V>emptyList());
286+
}
287+
return cache;
288+
}
289+
255290
private <V, E> long simulateReturnWalk(Graph<V, E> graph, V node, int maxSteps) {
256-
List<V> nb = new ArrayList<>(graph.getNeighbors(node));
257-
if (nb.isEmpty()) return maxSteps;
258-
V current = nb.get(rng.nextInt(nb.size()));
291+
Collection<V> nbrs = graph.getNeighbors(node);
292+
if (nbrs == null || nbrs.isEmpty()) return maxSteps;
293+
V current = pickRandom(nbrs);
259294
for (int step = 2; step <= maxSteps; step++) {
260295
if (current.equals(node)) return step;
261-
nb = new ArrayList<>(graph.getNeighbors(current));
262-
if (nb.isEmpty()) return maxSteps;
263-
current = nb.get(rng.nextInt(nb.size()));
296+
nbrs = graph.getNeighbors(current);
297+
if (nbrs == null || nbrs.isEmpty()) return maxSteps;
298+
current = pickRandom(nbrs);
264299
}
265300
return maxSteps;
266301
}
267302

303+
/** Pick a random element from a collection without copying it. */
304+
private <V> V pickRandom(Collection<V> coll) {
305+
int idx = rng.nextInt(coll.size());
306+
if (coll instanceof List) return ((List<V>) coll).get(idx);
307+
for (V v : coll) { if (idx-- == 0) return v; }
308+
throw new AssertionError("unreachable");
309+
}
310+
268311
private <V, E> double[][] buildTransitionMatrix(Graph<V, E> graph, List<V> nodeList, Map<V, Integer> idx) {
269312
int n = nodeList.size();
270313
double[][] P = new double[n][n];

0 commit comments

Comments
 (0)