175 lines
6.5 KiB
Java
175 lines
6.5 KiB
Java
package unit;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.Collections;
|
||
import java.util.HashSet;
|
||
import java.util.List;
|
||
|
||
/**
|
||
* Models vtkGeneralizedSurfaceNets3D::RequestData() auto-label collection.
|
||
*
|
||
* When no explicit segmentation labels are provided, the filter collects
|
||
* unique region IDs by iterating all numPts scalars:
|
||
*
|
||
* SLOW: std::find() on a growing std::vector<double> — O(numPts × numLabels)
|
||
* FAST: std::unordered_set<double> insertion test — O(numPts)
|
||
*
|
||
* CWE-407: VTK Filters/Meshing/vtkGeneralizedSurfaceNets3D.cxx:1150
|
||
*/
|
||
public class SurfaceNetsLabelCollectAlgorithm {
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Slow (defective) implementation.
|
||
// -------------------------------------------------------------------------
|
||
|
||
static class SlowCollect {
|
||
long totalOps = 0;
|
||
|
||
/** Collect unique non-negative region IDs in order of first appearance. */
|
||
List<Double> collect(double[] regions) {
|
||
List<Double> autoLabels = new ArrayList<>();
|
||
for (double regionId : regions) {
|
||
if (regionId >= 0) {
|
||
boolean found = false;
|
||
for (double existing : autoLabels) { // O(k) scan — the defect
|
||
totalOps++;
|
||
if (existing == regionId) {
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!found) {
|
||
autoLabels.add(regionId);
|
||
}
|
||
}
|
||
}
|
||
return autoLabels;
|
||
}
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Fast (fixed) implementation.
|
||
// -------------------------------------------------------------------------
|
||
|
||
static class FastCollect {
|
||
long totalOps = 0;
|
||
|
||
List<Double> collect(double[] regions) {
|
||
HashSet<Double> seen = new HashSet<>();
|
||
List<Double> autoLabels = new ArrayList<>();
|
||
for (double regionId : regions) {
|
||
totalOps++; // one O(1) hash op per point
|
||
if (regionId >= 0 && seen.add(regionId)) {
|
||
autoLabels.add(regionId);
|
||
}
|
||
}
|
||
Collections.sort(autoLabels); // deterministic ordering
|
||
return autoLabels;
|
||
}
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Helpers
|
||
// -------------------------------------------------------------------------
|
||
|
||
static double[] buildRegions(int numPts, int numLabels) {
|
||
double[] regions = new double[numPts];
|
||
for (int i = 0; i < numPts; i++) {
|
||
regions[i] = i % numLabels; // round-robin label assignment
|
||
}
|
||
return regions;
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Tests
|
||
// -------------------------------------------------------------------------
|
||
|
||
static int passed = 0;
|
||
static int total = 0;
|
||
|
||
static void check(String label, boolean condition) {
|
||
total++;
|
||
if (condition) {
|
||
passed++;
|
||
System.out.println(" PASS " + label);
|
||
} else {
|
||
System.out.println(" FAIL " + label);
|
||
}
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
System.out.println("=== SurfaceNetsLabelCollectAlgorithm ===");
|
||
|
||
// --- Correctness: small known input ---
|
||
{
|
||
double[] regions = {0, 1, 2, 1, 0, 3, -1, 2, 3};
|
||
SlowCollect slow = new SlowCollect();
|
||
FastCollect fast = new FastCollect();
|
||
List<Double> slowResult = slow.collect(regions);
|
||
List<Double> fastResult = fast.collect(regions);
|
||
|
||
check("small: slow finds 4 labels", slowResult.size() == 4);
|
||
check("small: fast finds 4 labels", fastResult.size() == 4);
|
||
// Both should contain {0,1,2,3}; fast is sorted
|
||
Collections.sort(slowResult);
|
||
check("small: results equal after sort", slowResult.equals(fastResult));
|
||
}
|
||
|
||
// --- Correctness: single label ---
|
||
{
|
||
double[] regions = {5, 5, 5, 5};
|
||
SlowCollect slow = new SlowCollect();
|
||
FastCollect fast = new FastCollect();
|
||
List<Double> sr = slow.collect(regions);
|
||
List<Double> fr = fast.collect(regions);
|
||
check("single-label: slow size==1", sr.size() == 1);
|
||
check("single-label: fast size==1", fr.size() == 1);
|
||
check("single-label: value==5.0", fr.get(0) == 5.0);
|
||
}
|
||
|
||
// --- Correctness: all negative (no output labels) ---
|
||
{
|
||
double[] regions = {-1, -2, -3};
|
||
SlowCollect slow = new SlowCollect();
|
||
FastCollect fast = new FastCollect();
|
||
check("all-neg: slow empty", slow.collect(regions).isEmpty());
|
||
check("all-neg: fast empty", fast.collect(regions).isEmpty());
|
||
}
|
||
|
||
// --- Performance: O(numPts × numLabels) vs O(numPts) ---
|
||
{
|
||
int numPts = 500_000;
|
||
int numLabels = 200;
|
||
double[] regions = buildRegions(numPts, numLabels);
|
||
|
||
SlowCollect slow = new SlowCollect();
|
||
FastCollect fast = new FastCollect();
|
||
|
||
long t0 = System.nanoTime();
|
||
List<Double> slowResult = slow.collect(regions);
|
||
long slowNs = System.nanoTime() - t0;
|
||
|
||
t0 = System.nanoTime();
|
||
List<Double> fastResult = fast.collect(regions);
|
||
long fastNs = System.nanoTime() - t0;
|
||
|
||
// Slow scan count: once every label is seen (after first numLabels pts),
|
||
// each subsequent point triggers a full numLabels scan → ≈ numPts × numLabels / 2
|
||
long slowOps = slow.totalOps;
|
||
long fastOps = fast.totalOps;
|
||
double ratio = (double) slowNs / fastNs;
|
||
|
||
System.out.printf(" INFO numPts=%d numLabels=%d slow_ops=%d fast_ops=%d ratio=%.1fx%n",
|
||
numPts, numLabels, slowOps, fastOps, ratio);
|
||
|
||
check("slow ops >> fast ops (>= 10x)", slowOps >= fastOps * 10);
|
||
check("fast ops == numPts", fastOps == numPts);
|
||
check("fast is meaningfully faster (>= 3x)", ratio >= 3.0);
|
||
check("label counts agree", slowResult.size() == fastResult.size());
|
||
}
|
||
|
||
System.out.println();
|
||
System.out.printf("%d/%d PASS%n", passed, total);
|
||
if (passed != total) System.exit(1);
|
||
}
|
||
}
|