panda3d/ogre: CWE-407 findings
panda3d-0001: NodePathCollection remove_duplicate_paths O(N^2) nested scan and remove_paths_from has_path O(N*M) -> fix with pset, 499x at N=1000 ogre-0001: GLSLProgramWriter std::find(inParams) O(A*O*P) in nested loop during GLSL shader code generation -> fix with unordered_set, 75x at A=200
This commit is contained in:
parent
780d7fde87
commit
75edb2a0a9
4 changed files with 351 additions and 148 deletions
|
|
@ -0,0 +1,32 @@
|
|||
# UNDF: (leave blank)
|
||||
--- a/Components/RTShaderSystem/src/OgreShaderGLSLProgramWriter.cpp
|
||||
+++ b/Components/RTShaderSystem/src/OgreShaderGLSLProgramWriter.cpp
|
||||
@@ -207,6 +207,10 @@ void GLSLProgramWriter::writeSourceCode(std::ostream& os, Program* program)
|
||||
|
||||
Function* curFunction = program->getMain();
|
||||
const ShaderParameterList& inParams = curFunction->getInputParameters();
|
||||
+ // Build an unordered set for O(1) membership queries, replacing the
|
||||
+ // per-operand O(P) std::find scan in the inner loop below.
|
||||
+ std::unordered_set<const Parameter*> inParamSet;
|
||||
+ for (const auto& p : inParams)
|
||||
+ inParamSet.insert(p.get());
|
||||
|
||||
writeFunctionTitle(os, curFunction);
|
||||
|
||||
@@ -222,13 +226,13 @@ void GLSLProgramWriter::writeSourceCode(std::ostream& os, Program* program)
|
||||
{
|
||||
redirectGlobalWrites(os, pFuncInvoc, inParams, parameterList);
|
||||
for (auto& operand : pFuncInvoc->getOperandList())
|
||||
{
|
||||
const ParameterPtr& param = operand.getParameter();
|
||||
if (gpuType != GPT_VERTEX_PROGRAM || param->getSemantic() != Parameter::SPS_TEXTURE_COORDINATES)
|
||||
continue;
|
||||
|
||||
- bool isInputParam = std::find(inParams.begin(), inParams.end(), param) != inParams.end();
|
||||
+ bool isInputParam = inParamSet.count(param.get()) > 0;
|
||||
|
||||
// Now that every texcoord is a vec4 (passed as vertex attributes) we
|
||||
// have to swizzle them according the desired type.
|
||||
if (isInputParam)
|
||||
operand.setMaskToParamType();
|
||||
}
|
||||
144
defects/ogre/unit/OgreTest.java
Normal file
144
defects/ogre/unit/OgreTest.java
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import java.util.*;
|
||||
|
||||
/**
|
||||
* CWE-407 simulation: OGRE3D GLSLProgramWriter inParams linear scan.
|
||||
*
|
||||
* Defect: ogre-0001
|
||||
* File: Components/RTShaderSystem/src/OgreShaderGLSLProgramWriter.cpp
|
||||
*
|
||||
* In writeSourceCode(), for every function invocation atom and for every
|
||||
* operand within that atom, the code calls:
|
||||
*
|
||||
* std::find(inParams.begin(), inParams.end(), param)
|
||||
*
|
||||
* where inParams is a vector<ParameterPtr>. This is O(P) per operand, giving
|
||||
* overall O(A * O * P) where:
|
||||
* A = atom instances (function invocations)
|
||||
* O = operands per atom
|
||||
* P = number of input parameters
|
||||
*
|
||||
* Fix: build an unordered_set<const Parameter*> from inParams before the loop,
|
||||
* reducing the per-operand lookup to O(1) → overall O(A * O).
|
||||
*/
|
||||
public class OgreTest {
|
||||
|
||||
/**
|
||||
* Simulates the defective O(A*O*P) inParams lookup:
|
||||
* for each atom, for each operand, do a linear scan of inParams.
|
||||
*
|
||||
* @param numAtoms number of function invocation atoms (A)
|
||||
* @param numOperands operands per atom (O)
|
||||
* @param numInParams number of input parameters (P)
|
||||
* @return total comparison operations performed
|
||||
*/
|
||||
static long defectiveShaderWrite(int numAtoms, int numOperands, int numInParams) {
|
||||
// Simulate inParams as a list of IDs
|
||||
List<Integer> inParams = new ArrayList<>(numInParams);
|
||||
for (int i = 0; i < numInParams; i++) {
|
||||
inParams.add(i);
|
||||
}
|
||||
|
||||
long ops = 0;
|
||||
for (int a = 0; a < numAtoms; a++) {
|
||||
for (int o = 0; o < numOperands; o++) {
|
||||
// The operand parameter ID — picks half from inParams, half not
|
||||
int paramId = (a * numOperands + o) % (numInParams * 2);
|
||||
// std::find: O(P) scan
|
||||
boolean found = false;
|
||||
for (int p = 0; p < numInParams; p++) {
|
||||
ops++;
|
||||
if (inParams.get(p).equals(paramId)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates the fixed O(A*O) inParams lookup using a hash set.
|
||||
*
|
||||
* @param numAtoms number of function invocation atoms (A)
|
||||
* @param numOperands operands per atom (O)
|
||||
* @param numInParams number of input parameters (P)
|
||||
* @return total comparison operations performed
|
||||
*/
|
||||
static long fixedShaderWrite(int numAtoms, int numOperands, int numInParams) {
|
||||
Set<Integer> inParamSet = new HashSet<>(numInParams);
|
||||
for (int i = 0; i < numInParams; i++) {
|
||||
inParamSet.add(i);
|
||||
}
|
||||
|
||||
long ops = 0;
|
||||
for (int a = 0; a < numAtoms; a++) {
|
||||
for (int o = 0; o < numOperands; o++) {
|
||||
int paramId = (a * numOperands + o) % (numInParams * 2);
|
||||
ops++; // hash-set lookup is O(1)
|
||||
boolean found = inParamSet.contains(paramId);
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// --- Correctness test: both must agree on which params are "input params" ---
|
||||
int P = 10, A = 5, O = 4;
|
||||
List<Integer> inParams = new ArrayList<>();
|
||||
for (int i = 0; i < P; i++) inParams.add(i);
|
||||
Set<Integer> inParamSet = new HashSet<>(inParams);
|
||||
|
||||
List<Boolean> defectResults = new ArrayList<>();
|
||||
List<Boolean> fixedResults = new ArrayList<>();
|
||||
for (int a = 0; a < A; a++) {
|
||||
for (int o = 0; o < O; o++) {
|
||||
int paramId = (a * O + o) % (P * 2);
|
||||
defectResults.add(inParams.contains(paramId));
|
||||
fixedResults.add(inParamSet.contains(paramId));
|
||||
}
|
||||
}
|
||||
assert defectResults.equals(fixedResults)
|
||||
: "Correctness check failed: defective and fixed produce different results";
|
||||
System.out.println("PASS: correctness verified for inParams membership lookup");
|
||||
|
||||
// --- Op-count ratio test ---
|
||||
System.out.println("\nOperation count ratio (defective / fixed):");
|
||||
int[][] configs = {
|
||||
{50, 8, 20},
|
||||
{100, 10, 50},
|
||||
{200, 12, 100},
|
||||
};
|
||||
for (int[] cfg : configs) {
|
||||
int atoms = cfg[0], operands = cfg[1], params = cfg[2];
|
||||
long dOps = defectiveShaderWrite(atoms, operands, params);
|
||||
long fOps = fixedShaderWrite(atoms, operands, params);
|
||||
double ratio = (double) dOps / fOps;
|
||||
System.out.printf(" A=%3d O=%2d P=%3d defect=%7d fixed=%5d ratio=%.1fx%n",
|
||||
atoms, operands, params, dOps, fOps, ratio);
|
||||
assert ratio > 2.0
|
||||
: "Expected ratio > 2x, got " + ratio + " for A=" + atoms + " O=" + operands + " P=" + params;
|
||||
}
|
||||
|
||||
// --- Wall-clock timing ---
|
||||
int bigA = 400, bigO = 20, bigP = 200;
|
||||
long t0 = System.nanoTime();
|
||||
defectiveShaderWrite(bigA, bigO, bigP);
|
||||
long defectNs = System.nanoTime() - t0;
|
||||
|
||||
t0 = System.nanoTime();
|
||||
fixedShaderWrite(bigA, bigO, bigP);
|
||||
long fixedNs = System.nanoTime() - t0;
|
||||
|
||||
double wallRatio = (double) defectNs / Math.max(fixedNs, 1);
|
||||
System.out.printf("%nWall-clock timing for A=%d O=%d P=%d:%n", bigA, bigO, bigP);
|
||||
System.out.printf(" defective: %6.2f ms%n", defectNs / 1e6);
|
||||
System.out.printf(" fixed: %6.2f ms%n", fixedNs / 1e6);
|
||||
System.out.printf(" ratio: %.1fx%n", wallRatio);
|
||||
|
||||
assert wallRatio > 1.5
|
||||
: "Expected wall-clock ratio > 1.5x, got " + wallRatio;
|
||||
|
||||
System.out.println("\nPASS: all assertions passed");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
# UNDF: (leave blank)
|
||||
--- a/panda/src/pgraph/nodePathCollection.cxx
|
||||
+++ b/panda/src/pgraph/nodePathCollection.cxx
|
||||
@@ -88,25 +88,25 @@ add_paths_from(const NodePathCollection &other) {
|
||||
*/
|
||||
void NodePathCollection::
|
||||
remove_paths_from(const NodePathCollection &other) {
|
||||
NodePaths new_paths;
|
||||
int num_paths = get_num_paths();
|
||||
+ // Build an unordered_set from other to enable O(1) membership checks,
|
||||
+ // reducing the overall complexity from O(N*M) to O(N+M).
|
||||
+ pset<NodePath> other_set;
|
||||
+ int other_num = other.get_num_paths();
|
||||
+ for (int i = 0; i < other_num; i++) {
|
||||
+ other_set.insert(other.get_path(i));
|
||||
+ }
|
||||
for (int i = 0; i < num_paths; i++) {
|
||||
NodePath path = get_path(i);
|
||||
- if (!other.has_path(path)) {
|
||||
+ if (other_set.find(path) == other_set.end()) {
|
||||
new_paths.push_back(path);
|
||||
}
|
||||
}
|
||||
_node_paths = new_paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any duplicate entries of the same NodePaths on this collection. If
|
||||
* a NodePath appears multiple times, the first appearance is retained;
|
||||
* subsequent appearances are removed.
|
||||
*/
|
||||
void NodePathCollection::
|
||||
remove_duplicate_paths() {
|
||||
NodePaths new_paths;
|
||||
-
|
||||
int num_paths = get_num_paths();
|
||||
+ // Use a set to track seen paths in O(log N) rather than O(N) per element,
|
||||
+ // reducing the overall complexity from O(N^2) to O(N log N).
|
||||
+ pset<NodePath> seen;
|
||||
for (int i = 0; i < num_paths; i++) {
|
||||
NodePath path = get_path(i);
|
||||
- bool duplicated = false;
|
||||
-
|
||||
- for (int j = 0; j < i && !duplicated; j++) {
|
||||
- duplicated = (path == get_path(j));
|
||||
- }
|
||||
-
|
||||
- if (!duplicated) {
|
||||
+ if (seen.insert(path).second) {
|
||||
new_paths.push_back(path);
|
||||
}
|
||||
}
|
||||
-
|
||||
_node_paths = new_paths;
|
||||
}
|
||||
|
|
@ -1,173 +1,145 @@
|
|||
package unit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Panda3D CWE-407 benchmark: std::find on vector vs O(1) unordered set/map.
|
||||
* CWE-407 simulation: Panda3D NodePathCollection O(N²) membership scan.
|
||||
*
|
||||
* Defect 1: panda/src/pgraph/camera.cxx line 252
|
||||
* Camera::remove_display_region — std::find on small_vector<DisplayRegion *>
|
||||
* Defect: panda3d-0001
|
||||
* File: panda/src/pgraph/nodePathCollection.cxx
|
||||
*
|
||||
* Defect 2: panda/src/display/graphicsOutput.cxx line 1623
|
||||
* GraphicsOutput::do_remove_display_region — std::find on pvector<PT(DisplayRegion)>
|
||||
* Called during window teardown — all N regions removed sequentially → O(N²).
|
||||
* remove_duplicate_paths() uses a nested O(N²) loop to check for duplicates:
|
||||
* for i in 0..N:
|
||||
* for j in 0..i:
|
||||
* if path[i] == path[j]: duplicated = true
|
||||
*
|
||||
* Fix 1: Replace small_vector<DisplayRegion *> with pset<DisplayRegion *>.
|
||||
* insert/erase/contains all O(1).
|
||||
* Fix 2: Replace pvector<PT(DisplayRegion)> with pmap<DisplayRegion *, PT(DisplayRegion)>.
|
||||
* find/erase O(1); iteration for active-region sort still works.
|
||||
* remove_paths_from() calls has_path() (O(N) scan) for each of N paths → O(N²).
|
||||
*
|
||||
* tickets: docs/tickets/panda3d-0001-camera-display-regions-linear-find.md
|
||||
* docs/tickets/panda3d-0002-graphics-output-total-display-regions-linear-find.md
|
||||
* Fix: use a HashSet for O(1) membership checks → O(N) total.
|
||||
*/
|
||||
public class Panda3DTest {
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// SLOW: mirrors small_vector + std::find
|
||||
// -----------------------------------------------------------------------
|
||||
static class SlowDisplayRegions {
|
||||
List<Integer> regions = new ArrayList<>(); // Integer = simulated pointer id
|
||||
|
||||
void add(int regionId) {
|
||||
regions.add(regionId);
|
||||
}
|
||||
|
||||
/** Exact mirror of camera.cxx line 251-255 / graphicsOutput.cxx line 1622-1628. */
|
||||
boolean remove(int regionId) {
|
||||
int idx = regions.indexOf(regionId); // std::find equivalent
|
||||
if (idx >= 0) {
|
||||
regions.remove(idx);
|
||||
return true;
|
||||
/** Simulates the defective O(N^2) remove_duplicate_paths(). */
|
||||
static List<Integer> removeDuplicatesDefective(List<Integer> paths) {
|
||||
List<Integer> result = new ArrayList<>();
|
||||
int n = paths.size();
|
||||
for (int i = 0; i < n; i++) {
|
||||
boolean duplicated = false;
|
||||
for (int j = 0; j < i && !duplicated; j++) {
|
||||
duplicated = paths.get(i).equals(paths.get(j));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// FAST: mirrors pset / pmap with O(1) ops
|
||||
// -----------------------------------------------------------------------
|
||||
static class FastDisplayRegions {
|
||||
Set<Integer> regions = new HashSet<>();
|
||||
|
||||
void add(int regionId) {
|
||||
regions.add(regionId);
|
||||
}
|
||||
|
||||
boolean remove(int regionId) {
|
||||
return regions.remove(regionId);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Bench harness
|
||||
// -----------------------------------------------------------------------
|
||||
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
|
||||
slow.run(); fast.run();
|
||||
long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000;
|
||||
long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000;
|
||||
double r = fOps > 0 ? (double) sOps / fOps : 0;
|
||||
System.out.printf(" %-52s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n",
|
||||
label, sMs, sOps, fMs, fOps, r);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Scenarios
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Scenario 1 (panda3d-0001): Camera display region removal.
|
||||
* Add N regions, then remove them all (e.g., camera reassignment loop).
|
||||
*/
|
||||
static Runnable slowCameraRemoveAll(int n) {
|
||||
return () -> {
|
||||
SlowDisplayRegions dr = new SlowDisplayRegions();
|
||||
for (int i = 0; i < n; i++) dr.add(i);
|
||||
for (int i = 0; i < n; i++) dr.remove(i);
|
||||
};
|
||||
}
|
||||
|
||||
static Runnable fastCameraRemoveAll(int n) {
|
||||
return () -> {
|
||||
FastDisplayRegions dr = new FastDisplayRegions();
|
||||
for (int i = 0; i < n; i++) dr.add(i);
|
||||
for (int i = 0; i < n; i++) dr.remove(i);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario 2 (panda3d-0002): Window teardown — all display regions destroyed.
|
||||
* Deferred shading pipeline: N = render passes (shadow maps + g-buffer + post).
|
||||
* Each DisplayRegion destructor calls do_remove_display_region.
|
||||
*/
|
||||
static Runnable slowWindowTeardown(int n) {
|
||||
return () -> {
|
||||
SlowDisplayRegions total = new SlowDisplayRegions();
|
||||
for (int i = 0; i < n; i++) total.add(i);
|
||||
// Teardown: regions destroyed in reverse-creation order (typical destructor order)
|
||||
for (int i = n - 1; i >= 0; i--) total.remove(i);
|
||||
};
|
||||
}
|
||||
|
||||
static Runnable fastWindowTeardown(int n) {
|
||||
return () -> {
|
||||
FastDisplayRegions total = new FastDisplayRegions();
|
||||
for (int i = 0; i < n; i++) total.add(i);
|
||||
for (int i = n - 1; i >= 0; i--) total.remove(i);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario 3: VR multi-eye with repeated camera reassignment.
|
||||
* Each eye reassignment removes from old camera and adds to new — repeated K times
|
||||
* across N display regions.
|
||||
*/
|
||||
static Runnable slowVRReassign(int n, int k) {
|
||||
return () -> {
|
||||
SlowDisplayRegions cam = new SlowDisplayRegions();
|
||||
for (int i = 0; i < n; i++) cam.add(i);
|
||||
for (int iter = 0; iter < k; iter++) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
cam.remove(i);
|
||||
cam.add(i);
|
||||
}
|
||||
if (!duplicated) {
|
||||
result.add(paths.get(i));
|
||||
}
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static Runnable fastVRReassign(int n, int k) {
|
||||
return () -> {
|
||||
FastDisplayRegions cam = new FastDisplayRegions();
|
||||
for (int i = 0; i < n; i++) cam.add(i);
|
||||
for (int iter = 0; iter < k; iter++) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
cam.remove(i);
|
||||
cam.add(i);
|
||||
}
|
||||
/** Simulates the fixed O(N log N) remove_duplicate_paths() using a set. */
|
||||
static List<Integer> removeDuplicatesFixed(List<Integer> paths) {
|
||||
List<Integer> result = new ArrayList<>();
|
||||
Set<Integer> seen = new LinkedHashSet<>();
|
||||
for (Integer p : paths) {
|
||||
if (seen.add(p)) {
|
||||
result.add(p);
|
||||
}
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Simulates the defective O(N*M) remove_paths_from(). */
|
||||
static List<Integer> removePathsFromDefective(List<Integer> self, List<Integer> other) {
|
||||
List<Integer> result = new ArrayList<>();
|
||||
for (Integer p : self) {
|
||||
// has_path() is O(M) linear scan
|
||||
if (!other.contains(p)) {
|
||||
result.add(p);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Simulates the fixed O(N+M) remove_paths_from() using a set. */
|
||||
static List<Integer> removePathsFromFixed(List<Integer> self, List<Integer> other) {
|
||||
Set<Integer> otherSet = new HashSet<>(other);
|
||||
List<Integer> result = new ArrayList<>();
|
||||
for (Integer p : self) {
|
||||
if (!otherSet.contains(p)) {
|
||||
result.add(p);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Count O(N^2) operations for defective remove_duplicate_paths. */
|
||||
static long opsDefectiveDeduplicate(int n) {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
ops += i; // inner loop runs i times
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
/** Count O(N) operations for fixed remove_duplicate_paths. */
|
||||
static long opsFixedDeduplicate(int n) {
|
||||
return n; // one hash-insert per element
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Panda3D CWE-407: std::find on vector vs O(1) hash set/map");
|
||||
System.out.println(" defect 1: panda/src/pgraph/camera.cxx line 252");
|
||||
System.out.println(" defect 2: panda/src/display/graphicsOutput.cxx line 1623");
|
||||
System.out.println();
|
||||
// --- Correctness tests ---
|
||||
List<Integer> paths = Arrays.asList(1, 2, 3, 2, 4, 1, 5);
|
||||
|
||||
int N = 800;
|
||||
long slowOps = (long) N * N / 2;
|
||||
List<Integer> defectResult = removeDuplicatesDefective(paths);
|
||||
List<Integer> fixedResult = removeDuplicatesFixed(paths);
|
||||
assert defectResult.equals(fixedResult)
|
||||
: "removeDuplicates: results differ: " + defectResult + " vs " + fixedResult;
|
||||
|
||||
bench(String.format("camera remove-all N=%d (panda3d-0001)", N),
|
||||
slowCameraRemoveAll(N), fastCameraRemoveAll(N), slowOps, N);
|
||||
List<Integer> allPaths = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
|
||||
List<Integer> toRemove = Arrays.asList(2, 4, 6);
|
||||
|
||||
bench(String.format("window teardown N=%d (panda3d-0002)", N),
|
||||
slowWindowTeardown(N), fastWindowTeardown(N), slowOps, N);
|
||||
List<Integer> defectRemove = removePathsFromDefective(allPaths, toRemove);
|
||||
List<Integer> fixedRemove = removePathsFromFixed(allPaths, toRemove);
|
||||
assert defectRemove.equals(fixedRemove)
|
||||
: "removePathsFrom: results differ: " + defectRemove + " vs " + fixedRemove;
|
||||
|
||||
bench(String.format("VR reassign N=%d x K=10 (panda3d-0001)", N),
|
||||
slowVRReassign(N, 10), fastVRReassign(N, 10), slowOps * 10, (long) N * 10);
|
||||
System.out.println("PASS: correctness verified for remove_duplicate_paths and remove_paths_from");
|
||||
|
||||
System.out.println();
|
||||
System.out.println("Fix 1 (camera.cxx): pset<DisplayRegion *> — insert/erase O(1).");
|
||||
System.out.println("Fix 2 (graphicsOutput.cxx): pmap<DisplayRegion *, PT(...)> — find/erase O(1).");
|
||||
System.out.println("See patches panda3d-0001-* and panda3d-0002-*");
|
||||
// --- Complexity ratio test ---
|
||||
int[] sizes = {100, 500, 1000};
|
||||
System.out.println("\nOperation count ratio (defective / fixed) for remove_duplicate_paths:");
|
||||
for (int n : sizes) {
|
||||
long defectOps = opsDefectiveDeduplicate(n);
|
||||
long fixedOps = opsFixedDeduplicate(n);
|
||||
double ratio = (double) defectOps / fixedOps;
|
||||
System.out.printf(" N=%4d defect=%6d fixed=%4d ratio=%.1fx%n",
|
||||
n, defectOps, fixedOps, ratio);
|
||||
assert ratio > 10.0
|
||||
: "Expected ratio > 10x at N=" + n + ", got " + ratio;
|
||||
}
|
||||
|
||||
// --- Wall-clock timing test ---
|
||||
int N = 2000;
|
||||
// Build a list with many duplicates (every path repeated 4 times)
|
||||
List<Integer> bigList = new ArrayList<>(N);
|
||||
for (int i = 0; i < N; i++) {
|
||||
bigList.add(i % (N / 4));
|
||||
}
|
||||
|
||||
long t0 = System.nanoTime();
|
||||
removeDuplicatesDefective(bigList);
|
||||
long defectNs = System.nanoTime() - t0;
|
||||
|
||||
t0 = System.nanoTime();
|
||||
removeDuplicatesFixed(bigList);
|
||||
long fixedNs = System.nanoTime() - t0;
|
||||
|
||||
double wallRatio = (double) defectNs / Math.max(fixedNs, 1);
|
||||
System.out.printf("%nWall-clock timing for N=%d remove_duplicate_paths:%n", N);
|
||||
System.out.printf(" defective: %6.2f ms%n", defectNs / 1e6);
|
||||
System.out.printf(" fixed: %6.2f ms%n", fixedNs / 1e6);
|
||||
System.out.printf(" ratio: %.1fx%n", wallRatio);
|
||||
|
||||
assert wallRatio > 2.0
|
||||
: "Expected wall-clock ratio > 2x at N=" + N + ", got " + wallRatio;
|
||||
|
||||
System.out.println("\nPASS: all assertions passed");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue