xonotic-0001: Mod_Mesh_GetTexture O(T) linear scan per draw call, 43x model_shared.c:4481 scans all textures for every quad/char/image drawn. Fix: hash table keyed on (name, drawflag, texflags, matflags). xonotic-0002: SV_ModelIndex O(M) linear scan, 81x sv_main.c:1421 scans model_precache[] (up to 8192) on every model ref. Fix: hash table on model filename. xonotic-0003: SV_SoundIndex O(S) linear scan, 66x sv_main.c:1484 scans sound_precache[] (up to 4096) on every sound ref. Fix: hash table on sound filename. xonotic-0004: S_FindName O(N) linked list scan, 72x snd_main.c:913 traverses linked list (has "TODO: hash table search?"). Fix: hash chain on sfx name. MOAD-0002 (Intertangle): CLEAN, god objects are idiomatic Quake engine. MOAD-0003 (Leaked Context): CLEAN, no thread_local usage. MOAD-0004 (Logged Secret): CLEAN, rcon_password uses CF_PRIVATE flag. MOAD-0005 (Thundering Herd): CLEAN, single-threaded cache access.
144 lines
3.7 KiB
C
144 lines
3.7 KiB
C
/**
|
|
* Unit test for xonotic-0002: SV_ModelIndex O(M) linear scan.
|
|
*
|
|
* Defect: sv_main.c SV_ModelIndex() linearly scans the model_precache array
|
|
* (up to MAX_MODELS = 8192 entries) on every model reference. Called from
|
|
* entity spawning, setmodel, baseline setup, and weapon model lookups.
|
|
*
|
|
* Fix: hash table for O(1) average lookup.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#define MAX_MODELS 8192
|
|
#define MAX_QPATH 128
|
|
#define NUM_LOOKUPS 100000
|
|
#define HASH_SIZE 512
|
|
|
|
static char model_precache[MAX_MODELS][MAX_QPATH];
|
|
static int num_models = 0;
|
|
|
|
/* Unpatched: O(M) linear scan */
|
|
static int find_model_linear(const char *filename)
|
|
{
|
|
int i;
|
|
for (i = 2; i < num_models; i++)
|
|
{
|
|
if (!model_precache[i][0])
|
|
return -1; /* empty slot = not found */
|
|
if (!strcmp(model_precache[i], filename))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* Patched: O(1) hash lookup */
|
|
static int model_hashtable[HASH_SIZE];
|
|
static int model_hashnext[MAX_MODELS];
|
|
|
|
static unsigned model_hash(const char *s)
|
|
{
|
|
unsigned hash = 0;
|
|
for (; *s; s++)
|
|
hash = hash * 31 + (unsigned char)*s;
|
|
return hash % HASH_SIZE;
|
|
}
|
|
|
|
static void model_hash_init(void)
|
|
{
|
|
int i;
|
|
for (i = 0; i < HASH_SIZE; i++)
|
|
model_hashtable[i] = -1;
|
|
for (i = 0; i < MAX_MODELS; i++)
|
|
model_hashnext[i] = -1;
|
|
for (i = 2; i < num_models; i++)
|
|
{
|
|
if (!model_precache[i][0])
|
|
continue;
|
|
unsigned h = model_hash(model_precache[i]);
|
|
model_hashnext[i] = model_hashtable[h];
|
|
model_hashtable[h] = i;
|
|
}
|
|
}
|
|
|
|
static int find_model_hash(const char *filename)
|
|
{
|
|
unsigned h = model_hash(filename);
|
|
int i;
|
|
for (i = model_hashtable[h]; i >= 0; i = model_hashnext[i])
|
|
if (!strcmp(model_precache[i], filename))
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int i, pass = 1;
|
|
clock_t start, end;
|
|
double linear_time, hash_time, ratio;
|
|
|
|
/* Populate: simulate a large map with many models */
|
|
num_models = 2000;
|
|
for (i = 2; i < num_models; i++)
|
|
snprintf(model_precache[i], MAX_QPATH, "progs/model_%04d.mdl", i);
|
|
|
|
model_hash_init();
|
|
|
|
/* Correctness */
|
|
for (i = 2; i < num_models; i++)
|
|
{
|
|
int lin = find_model_linear(model_precache[i]);
|
|
int hsh = find_model_hash(model_precache[i]);
|
|
if (lin != hsh)
|
|
{
|
|
printf("FAIL: mismatch at model %d: linear=%d hash=%d\n", i, lin, hsh);
|
|
pass = 0;
|
|
}
|
|
}
|
|
|
|
/* Not-found */
|
|
if (find_model_hash("progs/nonexistent.mdl") != -1)
|
|
{
|
|
printf("FAIL: hash found nonexistent model\n");
|
|
pass = 0;
|
|
}
|
|
|
|
/* Benchmark */
|
|
start = clock();
|
|
for (i = 0; i < NUM_LOOKUPS; i++)
|
|
{
|
|
int idx = 2 + (i % (num_models - 2));
|
|
volatile int r = find_model_linear(model_precache[idx]);
|
|
(void)r;
|
|
}
|
|
end = clock();
|
|
linear_time = (double)(end - start) / CLOCKS_PER_SEC;
|
|
|
|
start = clock();
|
|
for (i = 0; i < NUM_LOOKUPS; i++)
|
|
{
|
|
int idx = 2 + (i % (num_models - 2));
|
|
volatile int r = find_model_hash(model_precache[idx]);
|
|
(void)r;
|
|
}
|
|
end = clock();
|
|
hash_time = (double)(end - start) / CLOCKS_PER_SEC;
|
|
|
|
ratio = linear_time / (hash_time > 0 ? hash_time : 0.0001);
|
|
|
|
printf("xonotic-0002: SV_ModelIndex linear scan defect\n");
|
|
printf(" Models: %d, Lookups: %d\n", num_models - 2, NUM_LOOKUPS);
|
|
printf(" Linear: %.4fs, Hash: %.4fs, Ratio: %.1fx\n", linear_time, hash_time, ratio);
|
|
|
|
if (ratio < 2.0)
|
|
{
|
|
printf("FAIL: expected hash lookup to be at least 2x faster (got %.1fx)\n", ratio);
|
|
pass = 0;
|
|
}
|
|
|
|
printf("%s\n", pass ? "PASS" : "FAIL");
|
|
return pass ? 0 : 1;
|
|
}
|