New defects (all PASS): - exim-0001: same_hosts() MX-segment O(H²) → AVL set O(H log H), 10.5x at H=20 - minecraft-0001: DependencySorter.isCyclic no visited set O(E^D) → O(E), 342,000x at D=24 - minecraft-0002: PistonStructureResolver toPush ArrayList O(N²) → HashSet O(N) - minecraft-0003: RedstoneWireEvaluator Deque.contains O(N²) → HashSet O(N) - minecraft-0004: MoveThroughVillageGoal visited List O(N²) → HashSet O(N) - mpich-0001: group_lpid_to_rank O(N²) → HashMap O(N), 313x at N=1000 - ompi-0001: group_overlap process-name scan O(N×M) → HashMap O(N+M), 2048x - pcl-0001: RegionGrowing::getSegmentFromPoint O(C×S) → point_labels[] O(1), 50000x CLEAN confirmed: esbuild, express, koa, ktor, lucene, mpich-recvq, ompi-startup, prosody, roda, rust/rustc-wave2, signal-server, solana, wiredtiger, wireguard-tools, linux-kernel (pointer to linux/)
139 lines
4.9 KiB
Java
139 lines
4.9 KiB
Java
package unit;
|
||
|
||
import java.util.*;
|
||
|
||
/**
|
||
* pcl-0001: RegionGrowing::getSegmentFromPoint() clusters O(C×S) → point_labels[] O(1)
|
||
*
|
||
* In segmentation/include/pcl/segmentation/impl/region_growing.hpp::getSegmentFromPoint():
|
||
*
|
||
* for (const auto& i_segment : clusters_)
|
||
* {
|
||
* const auto it = std::find(i_segment.indices.cbegin(), i_segment.indices.cend(), index);
|
||
* if (it != i_segment.indices.cend()) { ... break; }
|
||
* }
|
||
*
|
||
* std::find is a linear O(S) scan per cluster; the outer loop is O(C).
|
||
* Total per-call cost: O(C × S) on average = O(N/2) worst case.
|
||
*
|
||
* The fix: point_labels_[index] is already set to the segment index by
|
||
* applySmoothRegionGrowingAlgorithm() and used verbatim by assembleRegions():
|
||
*
|
||
* clusters_[point_labels_[index]] // O(1) direct array index
|
||
*
|
||
* Severity: HIGH
|
||
* UNDF: assigned by generate_undf.py
|
||
*/
|
||
public class PclRegionGrowingGetSegmentTest {
|
||
|
||
static long slowOps = 0;
|
||
static long fastOps = 0;
|
||
|
||
/**
|
||
* Simulate the PCL clusters_ data structure:
|
||
* a list of clusters, each holding a list of point indices.
|
||
*/
|
||
static int[][] buildClusters(int numClusters, int pointsPerCluster) {
|
||
int[][] clusters = new int[numClusters][pointsPerCluster];
|
||
for (int c = 0; c < numClusters; c++) {
|
||
for (int p = 0; p < pointsPerCluster; p++) {
|
||
clusters[c][p] = c * pointsPerCluster + p;
|
||
}
|
||
}
|
||
return clusters;
|
||
}
|
||
|
||
/**
|
||
* SLOW: O(C × S) — scan all clusters, linear find in each.
|
||
* Mirrors the defective PCL implementation.
|
||
*/
|
||
static int[] getSegmentFromPointSlow(int[][] clusters, int queryIndex) {
|
||
for (int[] cluster : clusters) {
|
||
for (int idx : cluster) {
|
||
slowOps++;
|
||
if (idx == queryIndex) {
|
||
return cluster.clone();
|
||
}
|
||
}
|
||
}
|
||
return new int[0];
|
||
}
|
||
|
||
/**
|
||
* FAST: O(1) — use point_labels[] direct array index.
|
||
* Mirrors the patched PCL implementation.
|
||
*/
|
||
static int[] getSegmentFromPointFast(int[][] clusters, int[] pointLabels, int queryIndex) {
|
||
fastOps++; // one array lookup
|
||
int segmentIndex = pointLabels[queryIndex];
|
||
if (segmentIndex < 0 || segmentIndex >= clusters.length) return new int[0];
|
||
return clusters[segmentIndex].clone();
|
||
}
|
||
|
||
/**
|
||
* Build point_labels[]: maps each point index to its cluster index.
|
||
* This is what PCL's assembleRegions() establishes.
|
||
*/
|
||
static int[] buildPointLabels(int[][] clusters) {
|
||
int totalPoints = 0;
|
||
for (int[] c : clusters) totalPoints += c.length;
|
||
int[] labels = new int[totalPoints];
|
||
Arrays.fill(labels, -1);
|
||
for (int c = 0; c < clusters.length; c++) {
|
||
for (int idx : clusters[c]) {
|
||
labels[idx] = c;
|
||
}
|
||
}
|
||
return labels;
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
final int C = 500; // clusters (typical LiDAR scan: hundreds to thousands)
|
||
final int S = 200; // points per cluster
|
||
final int N = C * S; // total points = 100,000
|
||
|
||
int[][] clusters = buildClusters(C, S);
|
||
int[] pointLabels = buildPointLabels(clusters);
|
||
|
||
// Query every point once — simulates a user loop over all points
|
||
// (e.g. building a per-point cluster-membership map)
|
||
slowOps = 0;
|
||
fastOps = 0;
|
||
|
||
int[] slowResult = null;
|
||
int[] fastResult = null;
|
||
|
||
for (int q = 0; q < N; q++) {
|
||
slowResult = getSegmentFromPointSlow(clusters, q);
|
||
fastResult = getSegmentFromPointFast(clusters, pointLabels, q);
|
||
}
|
||
|
||
// Verify correctness on last query point
|
||
assert slowResult != null && fastResult != null;
|
||
assert slowResult.length == fastResult.length
|
||
: "result length mismatch: slow=" + slowResult.length + " fast=" + fastResult.length;
|
||
Arrays.sort(slowResult);
|
||
Arrays.sort(fastResult);
|
||
assert Arrays.equals(slowResult, fastResult)
|
||
: "result content mismatch";
|
||
|
||
// Spot-check a query in the middle of the last cluster (worst case for slow)
|
||
int worstQuery = N - 1;
|
||
int[] sw = getSegmentFromPointSlow(clusters, worstQuery);
|
||
int[] fw = getSegmentFromPointFast(clusters, pointLabels, worstQuery);
|
||
Arrays.sort(sw); Arrays.sort(fw);
|
||
assert Arrays.equals(sw, fw) : "worst-case query mismatch";
|
||
|
||
long ratio = slowOps / Math.max(fastOps, 1);
|
||
System.out.printf("N=%d C=%d S=%d%n", N, C, S);
|
||
System.out.printf("slowOps (O(C×S) per query) : %,d%n", slowOps);
|
||
System.out.printf("fastOps (O(1) per query) : %,d%n", fastOps);
|
||
System.out.printf("ratio : %,d×%n", ratio);
|
||
|
||
// Require at least 1000× improvement
|
||
assert ratio >= 1000
|
||
: "Expected >=1000x speedup, got " + ratio + "x";
|
||
|
||
System.out.println("PASS");
|
||
}
|
||
}
|