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

Commit e9ec4ca

Browse files
perf: pre-build adjacency sets once in GraphAnomalyDetector
Build adjSets map in computeRawMetrics() and reuse across computeClusteringCoeff(), computeNeighborDeviation(), and degree computation. Eliminates: - O(V) redundant graph.getNeighbors() JUNG collection allocations (one per vertex in the clustering loop's inner neighbor scan) - Per-vertex ArrayList + HashSet construction in clustering coefficient (now uses the shared pre-built Set directly) - Extra graph.getNeighbors() call in computeNeighborDeviation() - graph.degree() calls replaced by pre-computed set sizes On dense graphs the triangle-counting inner loop was the hot path, calling graph.getNeighbors(ni) for every neighbor of every vertex. Each JUNG call allocates a fresh Collection wrapper. The pre-built adjSets serve as O(1) membership oracles with zero per-call allocation.
1 parent 449049a commit e9ec4ca

1 file changed

Lines changed: 32 additions & 15 deletions

File tree

Gvisual/src/gvisual/GraphAnomalyDetector.java

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ public class GraphAnomalyDetector {
6060
private Map<String, Double> diversityMap;
6161
private Map<String, Double> neighborDevMap;
6262

63+
/** Pre-built adjacency sets — built once, reused across all per-vertex metrics. */
64+
private Map<String, Set<String>> adjSets;
65+
6366
/* Global statistics. */
6467
private double degreeMean, degreeStd;
6568
private double clusteringMean, clusteringStd;
@@ -375,17 +378,23 @@ private void computeRawMetrics() {
375378
diversityMap = new LinkedHashMap<String, Double>(n);
376379
neighborDevMap = new LinkedHashMap<String, Double>(n);
377380

378-
// Pre-compute degrees for neighbor-deviation calculation
379-
Map<String, Integer> rawDegrees = new HashMap<String, Integer>(n);
381+
// Build adjacency sets once — avoids repeated JUNG getNeighbors()
382+
// allocations across clustering, degree, and neighbor-deviation computations.
383+
adjSets = new HashMap<String, Set<String>>(n * 2);
384+
Map<String, Integer> rawDegrees = new HashMap<String, Integer>(n * 2);
380385
for (String v : vertices) {
381-
rawDegrees.put(v, graph.degree(v));
386+
Collection<String> nbrs = graph.getNeighbors(v);
387+
Set<String> nbrSet = nbrs == null ? Collections.emptySet()
388+
: new HashSet<String>(nbrs);
389+
adjSets.put(v, nbrSet);
390+
rawDegrees.put(v, nbrSet.size());
382391
}
383392

384393
for (String v : vertices) {
385394
int deg = rawDegrees.get(v);
386395
degreeMap.put(v, (double) deg);
387396

388-
// Local clustering coefficient
397+
// Local clustering coefficient (uses pre-built adjSets)
389398
clusteringMap.put(v, computeClusteringCoeff(v));
390399

391400
// Edge-type diversity (Shannon entropy)
@@ -399,28 +408,35 @@ private void computeRawMetrics() {
399408
/**
400409
* Computes the local clustering coefficient for a node.
401410
* <p>C(v) = 2 * triangles(v) / (deg(v) * (deg(v) - 1))</p>
411+
*
412+
* <p><b>Optimization:</b> Uses the pre-built {@code adjSets} map instead
413+
* of calling {@code graph.getNeighbors()} per neighbor. This eliminates
414+
* O(k) JUNG collection allocations per vertex (one per neighbor in the
415+
* triangle-counting loop) and avoids constructing a redundant HashSet
416+
* copy — the pre-built sets serve directly as O(1) membership oracles.</p>
402417
*/
403418
private double computeClusteringCoeff(String v) {
404-
Collection<String> neighbors = graph.getNeighbors(v);
405-
if (neighbors == null) return 0.0;
419+
Set<String> nbrSet = adjSets.get(v);
420+
if (nbrSet == null) return 0.0;
406421

407-
List<String> nbrs = new ArrayList<String>(neighbors);
408-
int k = nbrs.size();
422+
int k = nbrSet.size();
409423
if (k < 2) return 0.0;
410424

411-
// Count edges among neighbors (triangles)
425+
// Count edges among neighbors using pre-built adjacency sets.
426+
// For each neighbor ni, count how many of ni's neighbors are also
427+
// in v's neighbor set. Uses adjSets.get(ni) — O(1) map lookup —
428+
// instead of graph.getNeighbors(ni) which allocates a new Collection.
412429
int triangleEdges = 0;
413-
Set<String> nbrSet = new HashSet<String>(nbrs);
414-
for (int i = 0; i < k; i++) {
415-
Collection<String> niNeighbors = graph.getNeighbors(nbrs.get(i));
430+
for (String ni : nbrSet) {
431+
Set<String> niNeighbors = adjSets.get(ni);
416432
if (niNeighbors == null) continue;
417433
for (String nn : niNeighbors) {
418-
if (nbrSet.contains(nn) && !nn.equals(nbrs.get(i))) {
434+
if (nn != ni && nbrSet.contains(nn)) {
419435
triangleEdges++;
420436
}
421437
}
422438
}
423-
// Each triangle Edge counted twice (once from each endpoint)
439+
// Each edge counted twice (once from each endpoint)
424440
triangleEdges /= 2;
425441

426442
return (2.0 * triangleEdges) / (k * (k - 1));
@@ -462,10 +478,11 @@ private double computeEdgeDiversity(String v) {
462478

463479
/**
464480
* Computes absolute deviation of node's degree from its neighbors' mean degree.
481+
* Uses pre-built {@code adjSets} to avoid an additional JUNG getNeighbors() call.
465482
*/
466483
private double computeNeighborDeviation(String v,
467484
Map<String, Integer> rawDegrees) {
468-
Collection<String> neighbors = graph.getNeighbors(v);
485+
Set<String> neighbors = adjSets.get(v);
469486
if (neighbors == null || neighbors.isEmpty()) return 0.0;
470487

471488
double sum = 0.0;

0 commit comments

Comments
 (0)