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> 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 * 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 getAllObjectsDefective(XObject[] table) { List 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 seen -- O(1) per slot. static List getAllObjectsPatched(XObject[] table) { List results = new ArrayList<>(); Set 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 defResult = getAllObjectsDefective(smallTable); List 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"); } }