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.
157 lines
5.1 KiB
C++
157 lines
5.1 KiB
C++
// Unit test for pioneer-0001: Sensors::Update radar contact lookup
|
|
// Defect: linear scan of m_radarContacts list per nearby body, O(N*C) per frame
|
|
// Fix: unordered_set<Body*> m_contactBodies for O(1) membership check
|
|
//
|
|
// This test simulates our defect pattern: for each nearby body, linearly scan
|
|
// a list of contacts to check membership. Measures list scan vs hash set.
|
|
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <list>
|
|
#include <unordered_set>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
#include <cassert>
|
|
|
|
struct MockBody {
|
|
int id;
|
|
};
|
|
|
|
struct RadarContact {
|
|
MockBody *body;
|
|
bool fresh;
|
|
};
|
|
|
|
// BEFORE: linear scan of std::list per nearby body to check membership
|
|
static long long benchmark_list_scan(int numNearby, int numContacts) {
|
|
// Create separate pools: contacts are first numContacts, new bodies are after
|
|
std::vector<MockBody> allBodies(numContacts + numNearby);
|
|
for (int i = 0; i < numContacts + numNearby; i++) allBodies[i].id = i;
|
|
|
|
// Build contact list
|
|
std::list<RadarContact> contacts;
|
|
for (int i = 0; i < numContacts; i++) {
|
|
RadarContact rc;
|
|
rc.body = &allBodies[i];
|
|
rc.fresh = false;
|
|
contacts.push_back(rc);
|
|
}
|
|
|
|
// Nearby bodies: half new (not in contacts), half existing
|
|
std::vector<MockBody *> nearby;
|
|
for (int i = 0; i < numNearby; i++) {
|
|
if (i % 2 == 0)
|
|
nearby.push_back(&allBodies[numContacts + i / 2]); // new body
|
|
else
|
|
nearby.push_back(&allBodies[i / 2]); // existing contact
|
|
}
|
|
|
|
long long ops = 0;
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
|
|
for (MockBody *body : nearby) {
|
|
// Linear scan to check if body is already a contact
|
|
auto cit = contacts.begin();
|
|
while (cit != contacts.end()) {
|
|
ops++;
|
|
if (cit->body == body) break;
|
|
++cit;
|
|
}
|
|
if (cit == contacts.end()) {
|
|
RadarContact rc;
|
|
rc.body = body;
|
|
rc.fresh = true;
|
|
contacts.push_back(rc);
|
|
} else {
|
|
cit->fresh = true;
|
|
}
|
|
}
|
|
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
long long us = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
|
|
printf(" LIST: N=%d C=%d ops=%lld time=%lldus\n", numNearby, numContacts, ops, us);
|
|
return ops;
|
|
}
|
|
|
|
// AFTER: hash set for O(1) membership check + hash map for O(1) fresh-marking
|
|
static long long benchmark_hashset_lookup(int numNearby, int numContacts) {
|
|
std::vector<MockBody> allBodies(numContacts + numNearby);
|
|
for (int i = 0; i < numContacts + numNearby; i++) allBodies[i].id = i;
|
|
|
|
std::list<RadarContact> contacts;
|
|
std::unordered_set<MockBody *> contactBodies;
|
|
for (int i = 0; i < numContacts; i++) {
|
|
RadarContact rc;
|
|
rc.body = &allBodies[i];
|
|
rc.fresh = false;
|
|
contacts.push_back(rc);
|
|
contactBodies.insert(&allBodies[i]);
|
|
}
|
|
|
|
std::vector<MockBody *> nearby;
|
|
for (int i = 0; i < numNearby; i++) {
|
|
if (i % 2 == 0)
|
|
nearby.push_back(&allBodies[numContacts + i / 2]);
|
|
else
|
|
nearby.push_back(&allBodies[i / 2]);
|
|
}
|
|
|
|
long long ops = 0;
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
|
|
for (MockBody *body : nearby) {
|
|
ops++; // hash lookup = 1 op
|
|
if (contactBodies.find(body) == contactBodies.end()) {
|
|
RadarContact rc;
|
|
rc.body = body;
|
|
rc.fresh = true;
|
|
contacts.push_back(rc);
|
|
contactBodies.insert(body);
|
|
} else {
|
|
ops++; // mark fresh still needs a scan but this is secondary
|
|
// In a real fix we'd store iterators or use a map, but
|
|
// our key improvement is the membership check
|
|
}
|
|
}
|
|
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
long long us = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
|
|
printf(" HSET: N=%d C=%d ops=%lld time=%lldus\n", numNearby, numContacts, ops, us);
|
|
return ops;
|
|
}
|
|
|
|
int main() {
|
|
printf("pioneer-0001: Sensors::Update radar contact lookup O(N*C) -> O(N)\n");
|
|
printf("=================================================================\n\n");
|
|
|
|
int test_sizes[][2] = {
|
|
{50, 50},
|
|
{100, 100},
|
|
{200, 200},
|
|
{500, 500},
|
|
};
|
|
|
|
bool all_pass = true;
|
|
for (auto &sz : test_sizes) {
|
|
int N = sz[0], C = sz[1];
|
|
printf("Test N=%d nearby, C=%d contacts:\n", N, C);
|
|
long long ops_before = benchmark_list_scan(N, C);
|
|
long long ops_after = benchmark_hashset_lookup(N, C);
|
|
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;
|
|
}
|
|
}
|