227 lines
8 KiB
Java
227 lines
8 KiB
Java
package unit;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.HashSet;
|
||
import java.util.List;
|
||
|
||
/**
|
||
* Models vtkStaticCleanPolyData::RequestData() point-deduplication within cells.
|
||
*
|
||
* For each cell the filter collects unique mapped point IDs:
|
||
* SLOW: std::find() on a growing std::vector<vtkIdType> — O(npts²) per cell
|
||
* FAST: std::unordered_set<vtkIdType> insertion test — O(npts) per cell
|
||
*
|
||
* Total cost across C cells: O(C × npts²) vs O(C × npts).
|
||
*
|
||
* CWE-407: VTK Filters/Core/vtkStaticCleanPolyData.cxx:257,293,343,403
|
||
*/
|
||
public class StaticCleanPolyDataAlgorithm {
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Slow (defective) implementation — mirrors the C++ std::find approach.
|
||
// Returns the deduplicated point list and exposes total comparison ops.
|
||
// -------------------------------------------------------------------------
|
||
|
||
static class SlowDedup {
|
||
long totalOps = 0;
|
||
|
||
/** Deduplicate ptsInCell using linear scan; returns ordered unique list. */
|
||
List<Integer> dedup(int[] ptsInCell) {
|
||
List<Integer> cellIds = new ArrayList<>();
|
||
for (int ptId : ptsInCell) {
|
||
boolean found = false;
|
||
for (int existing : cellIds) { // O(k) scan — the defect
|
||
totalOps++;
|
||
if (existing == ptId) {
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!found) {
|
||
cellIds.add(ptId);
|
||
// account for the full scan that found nothing
|
||
if (!found) { /* already counted above */ }
|
||
}
|
||
}
|
||
return cellIds;
|
||
}
|
||
|
||
/** Process C cells each with the given point array. */
|
||
List<Integer> processCell(int[] pts) {
|
||
return dedup(pts);
|
||
}
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Fast (fixed) implementation — O(1) amortized via HashSet.
|
||
// -------------------------------------------------------------------------
|
||
|
||
static class FastDedup {
|
||
long totalOps = 0;
|
||
|
||
List<Integer> dedup(int[] ptsInCell) {
|
||
HashSet<Integer> seen = new HashSet<>();
|
||
List<Integer> cellIds = new ArrayList<>();
|
||
for (int ptId : ptsInCell) {
|
||
totalOps++; // one O(1) hash lookup per point
|
||
if (seen.add(ptId)) {
|
||
cellIds.add(ptId);
|
||
}
|
||
}
|
||
return cellIds;
|
||
}
|
||
|
||
List<Integer> processCell(int[] pts) {
|
||
return dedup(pts);
|
||
}
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Helper: build a cell with npts points, last dupFrac fraction are dupes
|
||
// -------------------------------------------------------------------------
|
||
|
||
static int[] buildCell(int npts, int uniquePts) {
|
||
// pts[0..uniquePts-1] are unique IDs; rest repeat from start
|
||
int[] pts = new int[npts];
|
||
for (int i = 0; i < npts; i++) {
|
||
pts[i] = i % uniquePts;
|
||
}
|
||
return pts;
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 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("=== StaticCleanPolyDataAlgorithm ===");
|
||
|
||
// --- Correctness: no duplicates ---
|
||
{
|
||
int[] pts = {10, 20, 30, 40};
|
||
SlowDedup slow = new SlowDedup();
|
||
FastDedup fast = new FastDedup();
|
||
List<Integer> slowResult = slow.dedup(pts);
|
||
List<Integer> fastResult = fast.dedup(pts);
|
||
|
||
check("no-dup: slow size == 4", slowResult.size() == 4);
|
||
check("no-dup: fast size == 4", fastResult.size() == 4);
|
||
check("no-dup: results equal", slowResult.equals(fastResult));
|
||
}
|
||
|
||
// --- Correctness: all duplicates ---
|
||
{
|
||
int[] pts = {7, 7, 7, 7, 7};
|
||
SlowDedup slow = new SlowDedup();
|
||
FastDedup fast = new FastDedup();
|
||
List<Integer> slowResult = slow.dedup(pts);
|
||
List<Integer> fastResult = fast.dedup(pts);
|
||
|
||
check("all-dup: slow size == 1", slowResult.size() == 1);
|
||
check("all-dup: fast size == 1", fastResult.size() == 1);
|
||
check("all-dup: both return [7]", slowResult.equals(fastResult));
|
||
}
|
||
|
||
// --- Correctness: mixed ---
|
||
{
|
||
int[] pts = {1, 2, 1, 3, 2, 4};
|
||
SlowDedup slow = new SlowDedup();
|
||
FastDedup fast = new FastDedup();
|
||
List<Integer> slowResult = slow.dedup(pts);
|
||
List<Integer> fastResult = fast.dedup(pts);
|
||
|
||
check("mixed: slow size == 4", slowResult.size() == 4);
|
||
check("mixed: fast size == 4", fastResult.size() == 4);
|
||
check("mixed: results equal", slowResult.equals(fastResult));
|
||
}
|
||
|
||
// --- Correctness: empty cell ---
|
||
{
|
||
int[] pts = {};
|
||
SlowDedup slow = new SlowDedup();
|
||
FastDedup fast = new FastDedup();
|
||
check("empty: slow size == 0", slow.dedup(pts).size() == 0);
|
||
check("empty: fast size == 0", fast.dedup(pts).size() == 0);
|
||
}
|
||
|
||
// --- Performance: O(npts²) vs O(npts) ---
|
||
{
|
||
// High-valence strip: npts = 128, all unique → slow must scan 0+1+2+...+127 = 8128 ops
|
||
int npts = 128;
|
||
int[] pts = buildCell(npts, npts); // all unique
|
||
|
||
SlowDedup slow = new SlowDedup();
|
||
FastDedup fast = new FastDedup();
|
||
|
||
int runs = 10_000;
|
||
|
||
long t0 = System.nanoTime();
|
||
for (int r = 0; r < runs; r++) {
|
||
slow.totalOps = 0;
|
||
slow.dedup(pts);
|
||
}
|
||
long slowNs = System.nanoTime() - t0;
|
||
long slowOpsPerCall = (npts * (npts - 1)) / 2; // expected: triangular number
|
||
|
||
t0 = System.nanoTime();
|
||
for (int r = 0; r < runs; r++) {
|
||
fast.totalOps = 0;
|
||
fast.dedup(pts);
|
||
}
|
||
long fastNs = System.nanoTime() - t0;
|
||
// Fast counts exactly npts hash ops per call
|
||
fast.totalOps = 0;
|
||
fast.dedup(pts);
|
||
long fastOpsPerCall = fast.totalOps;
|
||
|
||
double ratio = (double) slowNs / fastNs;
|
||
System.out.printf(" INFO npts=%d slow_ops=%d fast_ops=%d ratio=%.1fx%n",
|
||
npts, slowOpsPerCall, fastOpsPerCall, ratio);
|
||
|
||
check("slow op count = npts*(npts-1)/2",
|
||
slowOpsPerCall == (long) npts * (npts - 1) / 2);
|
||
check("fast op count = npts", fastOpsPerCall == npts);
|
||
check("fast is meaningfully faster (>= 1.5x)", ratio >= 1.5);
|
||
}
|
||
|
||
// --- Performance at larger scale: npts=256 ---
|
||
{
|
||
int npts = 256;
|
||
int[] pts = buildCell(npts, npts);
|
||
|
||
SlowDedup slow = new SlowDedup();
|
||
FastDedup fast = new FastDedup();
|
||
|
||
int runs = 5_000;
|
||
|
||
long t0 = System.nanoTime();
|
||
for (int r = 0; r < runs; r++) slow.dedup(pts);
|
||
long slowNs = System.nanoTime() - t0;
|
||
|
||
t0 = System.nanoTime();
|
||
for (int r = 0; r < runs; r++) fast.dedup(pts);
|
||
long fastNs = System.nanoTime() - t0;
|
||
|
||
double ratio = (double) slowNs / fastNs;
|
||
System.out.printf(" INFO npts=%d ratio=%.1fx%n", npts, ratio);
|
||
check("npts=256 fast is meaningfully faster (>= 1.5x)", ratio >= 1.5);
|
||
}
|
||
|
||
System.out.println();
|
||
System.out.printf("%d/%d PASS%n", passed, total);
|
||
if (passed != total) System.exit(1);
|
||
}
|
||
}
|