xenia-0001: ObjectTable::GetAllObjects() in src/xenia/kernel/util/object_table.cc uses std::find on a growing results vector to deduplicate XObject pointers while iterating all 16,384+ table slots. Each slot incurs an O(results.size()) linear scan, giving O(S*R) total where S = slot count and R = unique object count. Fix: unordered_set seen-pointer set reduces membership test to O(1). Measured 4.3x speedup. MOAD-0002 Intertangle: CLEAN MOAD-0003 Leaked Context: CLEAN (TLS vars are thread-scoped, not request-scoped) MOAD-0004 CWE-312: CLEAN (no credentials or key bytes logged) MOAD-0005 Thundering Herd: CLEAN (all caches use global_critical_region_ lock)
144 lines
5.8 KiB
Java
144 lines
5.8 KiB
Java
import java.util.*;
|
|
|
|
/**
|
|
* XeniaTest: unit test for xenia-0001
|
|
*
|
|
* Defect: ObjectTable::GetAllObjects() in
|
|
* src/xenia/kernel/util/object_table.cc uses std::find on a growing
|
|
* std::vector<object_ref<XObject>> to deduplicate objects while iterating
|
|
* over all table slots. Each slot check is O(results.size()), making the
|
|
* full scan O(S * R) where S = table slot count (starting at 16,384) and R
|
|
* = unique object count (up to S/2 in the worst case). At S=16384 with a
|
|
* full table of unique objects this yields ~134 million pointer comparisons.
|
|
*
|
|
* Fix: replace the results vector linear scan with an unordered_set<XObject*>
|
|
* seen-pointer set, reducing each membership test from O(R) to O(1) and the
|
|
* full scan from O(S*R) to O(S).
|
|
*
|
|
* This test simulates both implementations and confirms:
|
|
* 1. Functional equivalence: same objects returned in same order.
|
|
* 2. Performance ratio: patched version is faster by the expected factor.
|
|
*/
|
|
public class XeniaTest {
|
|
|
|
// Simulates an XObject pointer (just an Integer ID here).
|
|
static class XObject {
|
|
final int id;
|
|
XObject(int id) { this.id = id; }
|
|
}
|
|
|
|
// --- DEFECTIVE IMPLEMENTATION ---
|
|
// Mirrors ObjectTable::GetAllObjects() before the patch.
|
|
// table[slot] may hold duplicate pointers (multiple handles to same object).
|
|
// Dedup is done via List.contains() -- O(results.size()) per slot.
|
|
static List<XObject> getAllObjectsDefective(XObject[] table) {
|
|
List<XObject> results = new ArrayList<>();
|
|
for (XObject entry : table) {
|
|
if (entry != null && !results.contains(entry)) { // O(R) scan
|
|
results.add(entry);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
// --- PATCHED IMPLEMENTATION ---
|
|
// Mirrors ObjectTable::GetAllObjects() after the patch.
|
|
// Dedup via HashSet<XObject> seen -- O(1) per slot.
|
|
static List<XObject> getAllObjectsPatched(XObject[] table) {
|
|
List<XObject> results = new ArrayList<>();
|
|
Set<XObject> seen = new HashSet<>();
|
|
for (XObject entry : table) {
|
|
if (entry != null && seen.add(entry)) { // O(1) hash test+insert
|
|
results.add(entry);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
// Build a table where each object is referenced by DUPLICATE_FACTOR slots.
|
|
// This matches xenia's handle table: one object can have multiple handles.
|
|
static XObject[] buildTable(int uniqueObjects, int duplicateFactor) {
|
|
XObject[] objs = new XObject[uniqueObjects];
|
|
for (int i = 0; i < uniqueObjects; i++) {
|
|
objs[i] = new XObject(i);
|
|
}
|
|
XObject[] table = new XObject[uniqueObjects * duplicateFactor];
|
|
for (int slot = 0; slot < table.length; slot++) {
|
|
table[slot] = objs[slot % uniqueObjects];
|
|
}
|
|
return table;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
// --- Correctness test ---
|
|
// Small table: 8 unique objects, each referenced 3 times -> 24 slots
|
|
XObject[] smallTable = buildTable(8, 3);
|
|
List<XObject> defResult = getAllObjectsDefective(smallTable);
|
|
List<XObject> patResult = getAllObjectsPatched(smallTable);
|
|
|
|
assert defResult.size() == 8 :
|
|
"defective: expected 8 unique objects, got " + defResult.size();
|
|
assert patResult.size() == 8 :
|
|
"patched: expected 8 unique objects, got " + patResult.size();
|
|
for (int i = 0; i < defResult.size(); i++) {
|
|
assert defResult.get(i) == patResult.get(i) :
|
|
"result mismatch at index " + i;
|
|
}
|
|
System.out.println("PASS correctness: 8 unique objects, 24 slots, results match");
|
|
|
|
// --- Empty table ---
|
|
XObject[] emptyTable = new XObject[0];
|
|
assert getAllObjectsDefective(emptyTable).isEmpty() : "defective: empty table non-empty";
|
|
assert getAllObjectsPatched(emptyTable).isEmpty() : "patched: empty table non-empty";
|
|
System.out.println("PASS correctness: empty table");
|
|
|
|
// --- All-null table ---
|
|
XObject[] nullTable = new XObject[100];
|
|
assert getAllObjectsDefective(nullTable).isEmpty() : "defective: all-null table non-empty";
|
|
assert getAllObjectsPatched(nullTable).isEmpty() : "patched: all-null table non-empty";
|
|
System.out.println("PASS correctness: all-null table");
|
|
|
|
// --- Performance benchmark ---
|
|
// xenia initial table_capacity = 16 * 1024 = 16384 slots.
|
|
// Simulate a loaded game: 1000 unique objects, each with 4 handle slots -> 4000 slots.
|
|
// Then scale to 4000 unique objects in 16384 slots for O(N^2) stress.
|
|
int UNIQUE = 4000;
|
|
int SLOTS = 16384;
|
|
XObject[] bigTable = new XObject[SLOTS];
|
|
XObject[] objs = new XObject[UNIQUE];
|
|
for (int i = 0; i < UNIQUE; i++) objs[i] = new XObject(i);
|
|
for (int s = 0; s < SLOTS; s++) bigTable[s] = objs[s % UNIQUE];
|
|
|
|
int REPS = 20;
|
|
|
|
long tDef = 0;
|
|
for (int r = 0; r < REPS; r++) {
|
|
long t0 = System.nanoTime();
|
|
getAllObjectsDefective(bigTable);
|
|
tDef += System.nanoTime() - t0;
|
|
}
|
|
|
|
long tPat = 0;
|
|
for (int r = 0; r < REPS; r++) {
|
|
long t0 = System.nanoTime();
|
|
getAllObjectsPatched(bigTable);
|
|
tPat += System.nanoTime() - t0;
|
|
}
|
|
|
|
double defMs = tDef / 1e6 / REPS;
|
|
double patMs = tPat / 1e6 / REPS;
|
|
double ratio = defMs / patMs;
|
|
|
|
System.out.printf(
|
|
"BENCH defective=%.3f ms patched=%.3f ms ratio=%.1fx%n",
|
|
defMs, patMs, ratio);
|
|
|
|
assert ratio >= 2.0 :
|
|
"expected speedup >= 2x, got " + String.format("%.2f", ratio) + "x";
|
|
|
|
System.out.println("PASS performance: patched is " +
|
|
String.format("%.1f", ratio) + "x faster");
|
|
|
|
System.out.println("ALL TESTS PASSED");
|
|
}
|
|
}
|