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)
182 lines
4.7 KiB
C
182 lines
4.7 KiB
C
/*
|
|
* test_consistency_lookup.c
|
|
*
|
|
* Unit test for xash3d-0001: SV_FileInConsistencyList O(R*C) linear scan
|
|
*
|
|
* Demonstrates that our patched hash-based lookup reduces O(R*C) to O(R+C)
|
|
* when SV_TransferConsistencyInfo iterates all resources and checks each
|
|
* against our consistency list.
|
|
*
|
|
* Defect: SV_FileInConsistencyList does a linear scan of up to MAX_MODELS
|
|
* (512/4096) consistency entries for each of up to MAX_RESOURCES (8192)
|
|
* resources, producing millions of string comparisons per map load.
|
|
*
|
|
* Fix: Pre-build a hash set of consistency filenames, O(1) per lookup.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#define MAX_MODELS_TEST 512
|
|
#define MAX_RESOURCES_TEST 4096
|
|
#define MAX_QPATH 64
|
|
#define HASH_SIZE 256
|
|
|
|
/* --- Simulate our defective (linear scan) version --- */
|
|
|
|
typedef struct {
|
|
char filename[MAX_QPATH];
|
|
} consistency_t;
|
|
|
|
static consistency_t consistency_list[MAX_MODELS_TEST];
|
|
static int num_consistency_entries = 0;
|
|
|
|
static int linear_comparisons = 0;
|
|
|
|
static int file_in_consistency_list_linear(const char *filename)
|
|
{
|
|
int i;
|
|
for (i = 0; i < MAX_MODELS_TEST; i++)
|
|
{
|
|
if (!consistency_list[i].filename[0])
|
|
break;
|
|
linear_comparisons++;
|
|
if (strcmp(consistency_list[i].filename, filename) == 0)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* --- Simulate our patched (hash-based) version --- */
|
|
|
|
typedef struct hash_entry_s {
|
|
consistency_t *pc;
|
|
struct hash_entry_s *next;
|
|
} hash_entry_t;
|
|
|
|
static hash_entry_t *hash_table[HASH_SIZE];
|
|
static hash_entry_t hash_pool[MAX_MODELS_TEST];
|
|
static int hash_pool_used = 0;
|
|
static int 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 void build_consistency_hash(void)
|
|
{
|
|
int i;
|
|
memset(hash_table, 0, sizeof(hash_table));
|
|
hash_pool_used = 0;
|
|
|
|
for (i = 0; i < MAX_MODELS_TEST && consistency_list[i].filename[0]; i++)
|
|
{
|
|
unsigned int h = hash_key(consistency_list[i].filename);
|
|
hash_entry_t *e = &hash_pool[hash_pool_used++];
|
|
e->pc = &consistency_list[i];
|
|
e->next = hash_table[h];
|
|
hash_table[h] = e;
|
|
}
|
|
}
|
|
|
|
static int file_in_consistency_list_hash(const char *filename)
|
|
{
|
|
unsigned int h = hash_key(filename);
|
|
hash_entry_t *e;
|
|
|
|
for (e = hash_table[h]; e != NULL; e = e->next)
|
|
{
|
|
hash_comparisons++;
|
|
if (strcmp(e->pc->filename, filename) == 0)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* --- Test harness --- */
|
|
|
|
int main(void)
|
|
{
|
|
int i, found_linear, found_hash;
|
|
char resource_names[MAX_RESOURCES_TEST][MAX_QPATH];
|
|
int C, R;
|
|
double ratio;
|
|
int pass = 1;
|
|
|
|
/* Populate consistency list with C entries */
|
|
C = 200;
|
|
R = 2000;
|
|
|
|
for (i = 0; i < C; i++)
|
|
snprintf(consistency_list[i].filename, MAX_QPATH, "models/consistency_%04d.mdl", i);
|
|
num_consistency_entries = C;
|
|
|
|
/* Generate R resource names. Half match, half do not. */
|
|
for (i = 0; i < R; i++)
|
|
{
|
|
if (i < R / 2)
|
|
snprintf(resource_names[i], MAX_QPATH, "models/consistency_%04d.mdl", i % C);
|
|
else
|
|
snprintf(resource_names[i], MAX_QPATH, "models/resource_%04d.mdl", i);
|
|
}
|
|
|
|
/* Build hash for patched version */
|
|
build_consistency_hash();
|
|
|
|
/* Run linear version (defective) */
|
|
linear_comparisons = 0;
|
|
for (i = 0; i < R; i++)
|
|
file_in_consistency_list_linear(resource_names[i]);
|
|
|
|
/* Run hash version (patched) */
|
|
hash_comparisons = 0;
|
|
for (i = 0; i < R; i++)
|
|
file_in_consistency_list_hash(resource_names[i]);
|
|
|
|
/* Verify correctness */
|
|
for (i = 0; i < R; i++)
|
|
{
|
|
found_linear = file_in_consistency_list_linear(resource_names[i]);
|
|
found_hash = file_in_consistency_list_hash(resource_names[i]);
|
|
if (found_linear != found_hash)
|
|
{
|
|
fprintf(stderr, "FAIL: mismatch on resource %d: linear=%d hash=%d\n",
|
|
i, found_linear, found_hash);
|
|
pass = 0;
|
|
}
|
|
}
|
|
|
|
ratio = (double)linear_comparisons / (hash_comparisons > 0 ? hash_comparisons : 1);
|
|
|
|
printf("xash3d-0001: SV_FileInConsistencyList O(R*C) -> O(R+C)\n");
|
|
printf(" C=%d consistency entries, R=%d resources\n", C, R);
|
|
printf(" Linear comparisons: %d\n", linear_comparisons);
|
|
printf(" Hash comparisons: %d\n", hash_comparisons);
|
|
printf(" Ratio: %.1fx\n", ratio);
|
|
|
|
if (ratio < 5.0)
|
|
{
|
|
fprintf(stderr, "FAIL: expected at least 5x improvement, got %.1fx\n", ratio);
|
|
pass = 0;
|
|
}
|
|
|
|
if (pass)
|
|
{
|
|
printf("PASS\n");
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
printf("FAIL\n");
|
|
return 1;
|
|
}
|
|
}
|