164 lines
6.8 KiB
Java
164 lines
6.8 KiB
Java
package unit;
|
||
|
||
import java.util.*;
|
||
|
||
/**
|
||
* SDL3 CWE-407 benchmark: HasMappingChangeTracking linear scan vs O(1) hash set.
|
||
*
|
||
* Defect: src/joystick/SDL_gamepad.c HasMappingChangeTracking() lines 639-651
|
||
* Called inside PopMappingChangeTracking() for every joystick (line 670-687).
|
||
* changed_mappings is a pointer array scanned linearly each call.
|
||
* Total cost: O(n_joysticks × n_changed_mappings).
|
||
*
|
||
* Fix: Mirror changed_mappings into a SDL_HashTable (pointer set).
|
||
* HasMappingChangeTracking becomes one SDL_FindInHashTable call — O(1).
|
||
* SDL3 already uses SDL_HashTable for s_gamepadInstanceIDs in the same file.
|
||
*
|
||
* ticket: docs/tickets/sdl3-0001-gamepad-mapping-change-tracking-linear-scan.md
|
||
*/
|
||
public class SDL3Test {
|
||
|
||
// -----------------------------------------------------------------------
|
||
// SLOW: mirrors HasMappingChangeTracking — linear scan over pointer array
|
||
// -----------------------------------------------------------------------
|
||
static class SlowMappingChangeTracker {
|
||
List<Long> changedMappings = new ArrayList<>(); // Long = simulated pointer
|
||
|
||
void addMapping(long mappingPtr) {
|
||
changedMappings.add(mappingPtr);
|
||
}
|
||
|
||
/** Direct mirror of SDL_gamepad.c lines 645-649. */
|
||
boolean hasMappingChange(long mappingPtr) {
|
||
for (long m : changedMappings) {
|
||
if (m == mappingPtr) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/** PopMappingChangeTracking inner loop: O(n_joysticks * n_changed_mappings). */
|
||
int processJoysticks(long[] joystickMappings) {
|
||
int remapped = 0;
|
||
for (long jMapping : joystickMappings) {
|
||
if (hasMappingChange(jMapping)) {
|
||
remapped++;
|
||
}
|
||
}
|
||
return remapped;
|
||
}
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// FAST: O(1) via HashSet (mirrors proposed SDL_HashTable fix)
|
||
// -----------------------------------------------------------------------
|
||
static class FastMappingChangeTracker {
|
||
Set<Long> changedMappingsSet = new HashSet<>();
|
||
|
||
void addMapping(long mappingPtr) {
|
||
changedMappingsSet.add(mappingPtr);
|
||
}
|
||
|
||
boolean hasMappingChange(long mappingPtr) {
|
||
return changedMappingsSet.contains(mappingPtr);
|
||
}
|
||
|
||
int processJoysticks(long[] joystickMappings) {
|
||
int remapped = 0;
|
||
for (long jMapping : joystickMappings) {
|
||
if (hasMappingChange(jMapping)) {
|
||
remapped++;
|
||
}
|
||
}
|
||
return remapped;
|
||
}
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// 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: Bulk mapping reload (SDL_AddGamepadMappingsFromFile with full DB).
|
||
* M changed mappings × J joysticks (J = 8, simulating a haptics rig).
|
||
*/
|
||
static Runnable slowBulkReload(int m, int j) {
|
||
return () -> {
|
||
SlowMappingChangeTracker tracker = new SlowMappingChangeTracker();
|
||
for (int i = 0; i < m; i++) tracker.addMapping((long) (i + 1));
|
||
// Each joystick has a mapping pointer in [1..m]
|
||
long[] jMappings = new long[j];
|
||
for (int i = 0; i < j; i++) jMappings[i] = (long) (i % m + 1);
|
||
tracker.processJoysticks(jMappings);
|
||
};
|
||
}
|
||
|
||
static Runnable fastBulkReload(int m, int j) {
|
||
return () -> {
|
||
FastMappingChangeTracker tracker = new FastMappingChangeTracker();
|
||
for (int i = 0; i < m; i++) tracker.addMapping((long) (i + 1));
|
||
long[] jMappings = new long[j];
|
||
for (int i = 0; i < j; i++) jMappings[i] = (long) (i % m + 1);
|
||
tracker.processJoysticks(jMappings);
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Scenario 2: Full cross-product stress — M changed mappings × J joysticks,
|
||
* simulating a large tournament rig with many controllers and a full DB swap.
|
||
*/
|
||
static Runnable slowStress(int m, int j) {
|
||
return () -> {
|
||
SlowMappingChangeTracker tracker = new SlowMappingChangeTracker();
|
||
for (int i = 0; i < m; i++) tracker.addMapping((long) (i + 1));
|
||
long[] jMappings = new long[j];
|
||
// worst case: all joystick mappings are near the end of the array
|
||
for (int i = 0; i < j; i++) jMappings[i] = (long) (m - (i % 4) - 1);
|
||
tracker.processJoysticks(jMappings);
|
||
};
|
||
}
|
||
|
||
static Runnable fastStress(int m, int j) {
|
||
return () -> {
|
||
FastMappingChangeTracker tracker = new FastMappingChangeTracker();
|
||
for (int i = 0; i < m; i++) tracker.addMapping((long) (i + 1));
|
||
long[] jMappings = new long[j];
|
||
for (int i = 0; i < j; i++) jMappings[i] = (long) (m - (i % 4) - 1);
|
||
tracker.processJoysticks(jMappings);
|
||
};
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
System.out.println("SDL3 CWE-407: HasMappingChangeTracking linear scan vs O(1) hash set");
|
||
System.out.println(" defect: src/joystick/SDL_gamepad.c lines 639-651, 687");
|
||
System.out.println();
|
||
|
||
// SDL_gamepad_db.h ships 812 entries; simulate that scale
|
||
int M = 800; // changed mappings (full DB reload)
|
||
int J = 800; // joystick count (stress scenario)
|
||
long slowOps = (long) M * J;
|
||
long fastOps = J; // O(1) per joystick
|
||
|
||
bench(String.format("bulk-reload M=%d mappings J=8 joysticks", M),
|
||
slowBulkReload(M, 8), fastBulkReload(M, 8), (long) M * 8, 8);
|
||
|
||
bench(String.format("stress M=%d mappings J=%d joysticks", M, J),
|
||
slowStress(M, J), fastStress(M, J), slowOps, fastOps);
|
||
|
||
System.out.println();
|
||
System.out.println("Fix: add SDL_HashTable *changed_mappings_set to MappingChangeTracker.");
|
||
System.out.println(" SDL3 already has SDL_HashPointer/SDL_KeyMatchPointer.");
|
||
System.out.println(" See patch sdl3-0001-mapping-change-hash-set.patch");
|
||
}
|
||
}
|