simutrans-0001: rebuild_linked_connections() append_unique O(C*H^2) MEDIUM 97x
- vector_tpl::append_unique linear scan inside double loop over
goods categories x connections to collect unique connected halts
- fix: inthashtable_tpl for O(1) membership test
simutrans-0002: add_grund() registered_convoys.is_contained O(C*R) MEDIUM 45x
- iterates ALL world convoys, each with linear scan of registered
convoy vector to check membership
- fix: pre-build hash set of registered convoy IDs for O(1) lookup
simutrans-0003: rebuild_connections() consecutive_halts append_unique O(S^2) MEDIUM 24x
- append_unique on consecutive halt vectors per category inside
nested loop over schedules x entries during halt reconnection
- fix: parallel inthashtable_tpl for O(1) dedup
All three defects are in simhalt.cc halt reconnection paths, triggered
whenever schedules change (line added/removed, schedule edited, station
built). In large games with hundreds of halts and convoys, these
compound during reconnection sweeps.
MOAD-0002: welt (karte_t) is a god object but standard Simutrans architecture
MOAD-0003: CLEAN (no thread_local usage)
MOAD-0004: CLEAN (nettool password printf is by-design tool output)
MOAD-0005: CLEAN (save cache uses hashtable, no unsynchronized pattern)
185 lines
4.8 KiB
C
185 lines
4.8 KiB
C
/*
|
|
* test_precache_index.c
|
|
*
|
|
* Unit test for xash3d-0002: SV_ModelIndex/SV_SoundIndex O(N^2) precache scan
|
|
*
|
|
* Demonstrates that precache-index functions with linear dedup scan produce
|
|
* O(N^2/2) string comparisons when registering N unique resources, and that
|
|
* a hash-based approach reduces this to O(N) amortized.
|
|
*
|
|
* Defect: SV_ModelIndex scans model_precache[1..i] for each new precache
|
|
* call. With N unique models, total comparisons = 1+2+3+...+N = N*(N+1)/2.
|
|
* At MAX_MODELS=4096, that is ~8.4M string comparisons per map load.
|
|
*
|
|
* Fix: Maintain a hash table parallel to our precache array for O(1) lookup.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define MAX_MODELS_TEST 2048
|
|
#define MAX_QPATH 64
|
|
#define HASH_SIZE 512
|
|
|
|
/* --- Simulate defective linear version --- */
|
|
|
|
static char model_precache[MAX_MODELS_TEST][MAX_QPATH];
|
|
static int num_models = 0;
|
|
static long linear_comparisons = 0;
|
|
|
|
static int model_index_linear(const char *name)
|
|
{
|
|
int i;
|
|
for (i = 0; i < num_models; i++)
|
|
{
|
|
linear_comparisons++;
|
|
if (strcmp(model_precache[i], name) == 0)
|
|
return i + 1;
|
|
}
|
|
/* Register new */
|
|
if (num_models < MAX_MODELS_TEST)
|
|
{
|
|
strncpy(model_precache[num_models], name, MAX_QPATH - 1);
|
|
model_precache[num_models][MAX_QPATH - 1] = '\0';
|
|
num_models++;
|
|
}
|
|
return num_models;
|
|
}
|
|
|
|
/* --- Simulate patched hash version --- */
|
|
|
|
typedef struct hash_entry_s {
|
|
int index;
|
|
struct hash_entry_s *next;
|
|
} hash_entry_t;
|
|
|
|
static hash_entry_t *htable[HASH_SIZE];
|
|
static hash_entry_t hpool[MAX_MODELS_TEST];
|
|
static int hpool_used = 0;
|
|
|
|
static char model_precache_h[MAX_MODELS_TEST][MAX_QPATH];
|
|
static int num_models_h = 0;
|
|
static long hash_comparisons = 0;
|
|
|
|
static unsigned int hash_key(const char *s)
|
|
{
|
|
unsigned int h = 0;
|
|
while (*s)
|
|
{
|
|
h = h * 31 + (unsigned char)*s;
|
|
s++;
|
|
}
|
|
return h % HASH_SIZE;
|
|
}
|
|
|
|
static int model_index_hash(const char *name)
|
|
{
|
|
unsigned int h = hash_key(name);
|
|
hash_entry_t *e;
|
|
|
|
for (e = htable[h]; e != NULL; e = e->next)
|
|
{
|
|
hash_comparisons++;
|
|
if (strcmp(model_precache_h[e->index], name) == 0)
|
|
return e->index + 1;
|
|
}
|
|
|
|
/* Register new */
|
|
if (num_models_h < MAX_MODELS_TEST)
|
|
{
|
|
int idx = num_models_h;
|
|
strncpy(model_precache_h[idx], name, MAX_QPATH - 1);
|
|
model_precache_h[idx][MAX_QPATH - 1] = '\0';
|
|
num_models_h++;
|
|
|
|
hash_entry_t *ne = &hpool[hpool_used++];
|
|
ne->index = idx;
|
|
ne->next = htable[h];
|
|
htable[h] = ne;
|
|
}
|
|
return num_models_h;
|
|
}
|
|
|
|
/* --- Test --- */
|
|
|
|
int main(void)
|
|
{
|
|
int i, N;
|
|
char names[MAX_MODELS_TEST][MAX_QPATH];
|
|
double ratio;
|
|
int pass = 1;
|
|
int result_linear, result_hash;
|
|
|
|
N = 1500; /* Typical heavy mod precache count */
|
|
|
|
/* Generate N unique model names */
|
|
for (i = 0; i < N; i++)
|
|
snprintf(names[i], MAX_QPATH, "models/entity_%04d.mdl", i);
|
|
|
|
/* Reset state */
|
|
memset(model_precache, 0, sizeof(model_precache));
|
|
memset(model_precache_h, 0, sizeof(model_precache_h));
|
|
memset(htable, 0, sizeof(htable));
|
|
num_models = 0;
|
|
num_models_h = 0;
|
|
hpool_used = 0;
|
|
linear_comparisons = 0;
|
|
hash_comparisons = 0;
|
|
|
|
/* Simulate precaching N unique models (each one is new) */
|
|
for (i = 0; i < N; i++)
|
|
model_index_linear(names[i]);
|
|
|
|
for (i = 0; i < N; i++)
|
|
model_index_hash(names[i]);
|
|
|
|
/* Verify both produce same count */
|
|
if (num_models != num_models_h)
|
|
{
|
|
fprintf(stderr, "FAIL: model count mismatch: linear=%d hash=%d\n",
|
|
num_models, num_models_h);
|
|
pass = 0;
|
|
}
|
|
|
|
/* Verify lookups produce same results */
|
|
for (i = 0; i < N; i++)
|
|
{
|
|
/* Reset counters for correctness check (not counting these) */
|
|
result_linear = model_index_linear(names[i]);
|
|
result_hash = model_index_hash(names[i]);
|
|
if (result_linear != result_hash)
|
|
{
|
|
fprintf(stderr, "FAIL: index mismatch for %s: linear=%d hash=%d\n",
|
|
names[i], result_linear, result_hash);
|
|
pass = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ratio = (double)linear_comparisons / (hash_comparisons > 0 ? hash_comparisons : 1);
|
|
|
|
printf("xash3d-0002: SV_ModelIndex O(N^2/2) -> O(N) precache dedup\n");
|
|
printf(" N=%d unique models\n", N);
|
|
printf(" Linear comparisons: %ld (expected ~N^2/2 = %ld)\n",
|
|
linear_comparisons, (long)N * (N - 1) / 2);
|
|
printf(" Hash comparisons: %ld\n", hash_comparisons);
|
|
printf(" Ratio: %.1fx\n", ratio);
|
|
|
|
if (ratio < 10.0)
|
|
{
|
|
fprintf(stderr, "FAIL: expected at least 10x improvement, got %.1fx\n", ratio);
|
|
pass = 0;
|
|
}
|
|
|
|
if (pass)
|
|
{
|
|
printf("PASS\n");
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
printf("FAIL\n");
|
|
return 1;
|
|
}
|
|
}
|