pioneer-0001: Sensors::Update m_radarContacts linear scan O(N*C) per frame MEDIUM-HIGH, 250x at N=C=500. Hash set for O(1) membership check. pioneer-0002: Faction::IsClaimed m_ownedsystemlist linear scan O(S*F*C) MEDIUM, 219x at C=500. std::set for O(log C) lookup during sector gen. pioneer-0003: SectorView::GetDisplayMode m_route std::find_if O(S*R) per frame MEDIUM, 50x at S=5000 R=50. Hash set for O(1) route membership. MOAD-0002 (Intertangle): Pi class is god object but architectural, not patchable. MOAD-0003 (Leaked Context): CLEAN, thread_local used only for task graph internals. MOAD-0004 (Logged Secret): CLEAN, no credentials in codebase (space sim). MOAD-0005 (Thundering Herd): CLEAN, GalaxyCache uses map with proper locking. 3/3 unit tests PASS.
135 lines
4.4 KiB
C++
135 lines
4.4 KiB
C++
// Unit test for pioneer-0003: SectorView::GetDisplayMode route membership O(S*R)
|
|
// Defect: std::find_if on m_route vector per system in render loop, O(S*R) per frame
|
|
// Fix: unordered_set for O(1) route membership check
|
|
//
|
|
// GetDisplayMode is called per visible system in DrawNearSector. With DRAW_RAD=5
|
|
// that's 11^3 = 1331 sectors, each with ~5-20 systems = thousands of calls per frame.
|
|
// Each call does a linear scan of m_route (up to ~50 waypoints).
|
|
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <vector>
|
|
#include <unordered_set>
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <functional>
|
|
|
|
struct SystemPath {
|
|
int sectorX, sectorY, sectorZ;
|
|
unsigned int systemIndex;
|
|
|
|
SystemPath() : sectorX(0), sectorY(0), sectorZ(0), systemIndex(0) {}
|
|
SystemPath(int x, int y, int z, unsigned int si) : sectorX(x), sectorY(y), sectorZ(z), systemIndex(si) {}
|
|
|
|
bool IsSameSystem(const SystemPath &b) const {
|
|
return sectorX == b.sectorX && sectorY == b.sectorY
|
|
&& sectorZ == b.sectorZ && systemIndex == b.systemIndex;
|
|
}
|
|
bool operator==(const SystemPath &b) const { return IsSameSystem(b); }
|
|
};
|
|
|
|
struct SystemPathHash {
|
|
size_t operator()(const SystemPath &p) const {
|
|
size_t h = std::hash<int>()(p.sectorX);
|
|
h ^= std::hash<int>()(p.sectorY) + 0x9e3779b9 + (h << 6) + (h >> 2);
|
|
h ^= std::hash<int>()(p.sectorZ) + 0x9e3779b9 + (h << 6) + (h >> 2);
|
|
h ^= std::hash<unsigned int>()(p.systemIndex) + 0x9e3779b9 + (h << 6) + (h >> 2);
|
|
return h;
|
|
}
|
|
};
|
|
|
|
// BEFORE: linear scan per system
|
|
static long long benchmark_linear(int numSystems, int routeSize) {
|
|
std::vector<SystemPath> route;
|
|
for (int i = 0; i < routeSize; i++) {
|
|
route.push_back(SystemPath(i * 3, i * 2, i, i % 20));
|
|
}
|
|
|
|
std::vector<SystemPath> systems;
|
|
for (int i = 0; i < numSystems; i++) {
|
|
systems.push_back(SystemPath(i % 50, (i / 50) % 50, i / 2500, i % 30));
|
|
}
|
|
|
|
long long ops = 0;
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
|
|
for (const auto &system : systems) {
|
|
bool found = false;
|
|
for (const auto &r : route) {
|
|
ops++;
|
|
if (system.IsSameSystem(r)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
(void)found;
|
|
}
|
|
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
auto us = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
|
|
printf(" LINEAR: S=%d R=%d ops=%lld time=%lldus\n", numSystems, routeSize, ops, us);
|
|
return ops;
|
|
}
|
|
|
|
// AFTER: hash set
|
|
static long long benchmark_hashset(int numSystems, int routeSize) {
|
|
std::unordered_set<SystemPath, SystemPathHash> routeSet;
|
|
for (int i = 0; i < routeSize; i++) {
|
|
routeSet.insert(SystemPath(i * 3, i * 2, i, i % 20));
|
|
}
|
|
|
|
std::vector<SystemPath> systems;
|
|
for (int i = 0; i < numSystems; i++) {
|
|
systems.push_back(SystemPath(i % 50, (i / 50) % 50, i / 2500, i % 30));
|
|
}
|
|
|
|
long long ops = 0;
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
|
|
for (const auto &system : systems) {
|
|
ops++;
|
|
bool found = routeSet.count(system) > 0;
|
|
(void)found;
|
|
}
|
|
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
auto us = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
|
|
printf(" HSET: S=%d R=%d ops=%lld time=%lldus\n", numSystems, routeSize, ops, us);
|
|
return ops;
|
|
}
|
|
|
|
int main() {
|
|
printf("pioneer-0003: SectorView route membership O(S*R) -> O(S)\n");
|
|
printf("=================================================================\n\n");
|
|
|
|
int test_sizes[][2] = {
|
|
{500, 20},
|
|
{1000, 30},
|
|
{2000, 50},
|
|
{5000, 50},
|
|
};
|
|
|
|
bool all_pass = true;
|
|
for (auto &sz : test_sizes) {
|
|
int S = sz[0], R = sz[1];
|
|
printf("Test S=%d systems, R=%d route waypoints:\n", S, R);
|
|
long long ops_before = benchmark_linear(S, R);
|
|
long long ops_after = benchmark_hashset(S, R);
|
|
double ratio = (double)ops_before / (double)ops_after;
|
|
printf(" Ratio: %.1fx fewer operations\n\n", ratio);
|
|
if (ratio < 2.0) {
|
|
printf(" FAIL: expected at least 2x improvement\n");
|
|
all_pass = false;
|
|
}
|
|
}
|
|
|
|
printf("=================================================================\n");
|
|
if (all_pass) {
|
|
printf("PASS: all tests passed\n");
|
|
return 0;
|
|
} else {
|
|
printf("FAIL: some tests failed\n");
|
|
return 1;
|
|
}
|
|
}
|