java-topology/defects/ogre/unit/OGRETest.java

170 lines
6.8 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.*;
/**
* OGRETest — Java analogs of three OGRE CWE-407 defects.
*
* Defect 1 (ogre-0001): Node::~Node — std::find on vector of queued-update nodes.
* Slow: ArrayList.remove(Object) — O(N) scan per node destroyed.
* Fast: LinkedHashSet.remove(Object) — O(1) per node destroyed.
*
* Defect 2 (ogre-0002): ResourceGroupManager::_notifyAllResourcesRemoved — std::find
* in nested loop erasing resources from a list.
* Slow: ArrayList.remove(Object) inside an erase loop — O(N²).
* Fast: Build HashSet of to-remove pointers, single-pass removeIf — O(N).
*
* Defect 3 (ogre-0003): RibbonTrail::clearChain — std::find on parallel index vector
* to reverse-map chain-index → node.
* Slow: ArrayList.indexOf(chainIndex) — O(N).
* Fast: HashMap<Integer,Object> reverse map — O(1).
*/
public class OGRETest {
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
// warmup
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 ratio = fOps > 0 ? (double) sOps / fOps : 0;
System.out.printf(" %-56s slow:%5dms (%,d ops) fast:%5dms (%,d ops) speedup:%.0fx%n",
label, sMs, sOps, fMs, fOps, ratio);
}
// -----------------------------------------------------------------------
// ogre-0001: Node queued-update removal during bulk destruction
// -----------------------------------------------------------------------
static void benchNodeQueuedUpdates(int N) {
// SLOW: vector — O(N) scan per node destruction
Runnable slow = () -> {
List<Object> queue = new ArrayList<>(N);
Object[] nodes = new Object[N];
for (int i = 0; i < N; i++) {
nodes[i] = new Object();
queue.add(nodes[i]);
}
// simulate N node destructions: each does std::find + erase
for (int i = 0; i < N; i++) {
queue.remove(nodes[i]); // O(N) scan
}
};
// FAST: unordered_set — O(1) erase per node destruction
Runnable fast = () -> {
Set<Object> queue = new LinkedHashSet<>(N * 2);
Object[] nodes = new Object[N];
for (int i = 0; i < N; i++) {
nodes[i] = new Object();
queue.add(nodes[i]);
}
for (int i = 0; i < N; i++) {
queue.remove(nodes[i]); // O(1) hash remove
}
};
long sOps = (long) N * N / 2; // average scan length N/2 × N destructions
long fOps = N;
bench(String.format("ogre-0001 Node::~Node queued-update scan N=%d", N),
slow, fast, sOps, fOps);
}
// -----------------------------------------------------------------------
// ogre-0002: ResourceGroupManager notify-removed — nested find+erase
// -----------------------------------------------------------------------
static void benchResourceGroupRemove(int R) {
// SLOW: std::find on list for each item in arDel — O(R²)
Runnable slow = () -> {
List<Object> resourceList = new ArrayList<>(R);
for (int i = 0; i < R; i++) resourceList.add(new Object());
List<Object> arDel = new ArrayList<>(resourceList); // remove all
for (Object item : arDel) {
resourceList.remove(item); // O(R) scan each time
}
};
// FAST: build HashSet, single-pass removeIf — O(R)
Runnable fast = () -> {
List<Object> resourceList = new ArrayList<>(R);
for (int i = 0; i < R; i++) resourceList.add(new Object());
List<Object> arDel = new ArrayList<>(resourceList);
Set<Object> toRemove = new HashSet<>(arDel);
resourceList.removeIf(toRemove::contains); // O(R) single pass
};
long sOps = (long) R * R;
long fOps = R;
bench(String.format("ogre-0002 ResourceGroupManager::_notifyRemoved R=%d", R),
slow, fast, sOps, fOps);
}
// -----------------------------------------------------------------------
// ogre-0003: RibbonTrail::clearChain — parallel-vector reverse lookup
// -----------------------------------------------------------------------
static void benchRibbonTrailClearChain(int K, int clears) {
Object[] nodes = new Object[K];
for (int i = 0; i < K; i++) nodes[i] = new Object();
// SLOW: parallel arrays, indexOf for reverse lookup
Runnable slow = () -> {
List<Object> nodeList = new ArrayList<>(Arrays.asList(nodes));
List<Integer> chainSegments = new ArrayList<>();
for (int i = 0; i < K; i++) chainSegments.add(i);
long dummy = 0;
for (int c = 0; c < clears; c++) {
int chainIndex = c % K;
// std::find on mNodeToChainSegment — O(K)
int pos = chainSegments.indexOf(chainIndex);
if (pos >= 0) dummy += pos;
}
if (dummy < 0) System.out.println("never");
};
// FAST: HashMap for reverse lookup — O(1) per clear
Runnable fast = () -> {
Map<Integer, Object> chainToNode = new HashMap<>(K * 2);
for (int i = 0; i < K; i++) chainToNode.put(i, nodes[i]);
long dummy = 0;
for (int c = 0; c < clears; c++) {
int chainIndex = c % K;
// HashMap.get — O(1)
Object n = chainToNode.get(chainIndex);
if (n != null) dummy++;
}
if (dummy < 0) System.out.println("never");
};
long sOps = (long) clears * K;
long fOps = clears;
bench(String.format("ogre-0003 RibbonTrail::clearChain K=%d clears=%d", K, clears),
slow, fast, sOps, fOps);
}
public static void main(String[] args) {
System.out.println("OGRE CWE-407 defect benchmarks");
System.out.println("=".repeat(100));
System.out.println("\n[ogre-0001] Node::~Node — msQueuedUpdates linear scan");
benchNodeQueuedUpdates(500);
benchNodeQueuedUpdates(2_000);
benchNodeQueuedUpdates(10_000);
System.out.println("\n[ogre-0002] ResourceGroupManager::_notifyAllResourcesRemoved");
benchResourceGroupRemove(500);
benchResourceGroupRemove(2_000);
benchResourceGroupRemove(10_000);
System.out.println("\n[ogre-0003] RibbonTrail::clearChain — parallel-vector reverse lookup");
benchRibbonTrailClearChain(50, 100_000);
benchRibbonTrailClearChain(200, 100_000);
benchRibbonTrailClearChain(1_000, 100_000);
}
}