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.
142 lines
3.7 KiB
C
142 lines
3.7 KiB
C
/**
|
|
* Unit test for xonotic-0004: S_FindName O(N) linked list scan.
|
|
*
|
|
* Defect: snd_main.c S_FindName() traverses a linked list of known sfx entries
|
|
* to find a sound by name. The code itself has a "TODO: hash table search?"
|
|
* comment. Called every time a sound is played, precached, or referenced.
|
|
* With many sounds loaded, each lookup is O(N).
|
|
*
|
|
* Fix: hash table on sfx name for O(1) average lookup.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#define MAX_SFX 2000
|
|
#define NUM_LOOKUPS 100000
|
|
#define SFX_HASHSIZE 256
|
|
|
|
typedef struct sfx_s {
|
|
char name[64];
|
|
struct sfx_s *next;
|
|
struct sfx_s *hashnext;
|
|
} sfx_t;
|
|
|
|
static sfx_t sfx_pool[MAX_SFX];
|
|
static sfx_t *known_sfx = NULL;
|
|
static sfx_t *sfx_hashtable[SFX_HASHSIZE];
|
|
|
|
static unsigned sfx_hash(const char *name)
|
|
{
|
|
unsigned hash = 0;
|
|
const unsigned char *p;
|
|
for (p = (const unsigned char *)name; *p; p++)
|
|
hash = hash * 31 + *p;
|
|
return hash % SFX_HASHSIZE;
|
|
}
|
|
|
|
/* Unpatched: O(N) linked list scan */
|
|
static sfx_t *find_sfx_linear(const char *name)
|
|
{
|
|
sfx_t *sfx;
|
|
for (sfx = known_sfx; sfx != NULL; sfx = sfx->next)
|
|
if (!strcmp(sfx->name, name))
|
|
return sfx;
|
|
return NULL;
|
|
}
|
|
|
|
/* Patched: O(1) hash lookup */
|
|
static sfx_t *find_sfx_hash(const char *name)
|
|
{
|
|
unsigned h = sfx_hash(name);
|
|
sfx_t *sfx;
|
|
for (sfx = sfx_hashtable[h]; sfx != NULL; sfx = sfx->hashnext)
|
|
if (!strcmp(sfx->name, name))
|
|
return sfx;
|
|
return NULL;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int i, pass = 1;
|
|
clock_t start, end;
|
|
double linear_time, hash_time, ratio;
|
|
|
|
/* Build linked list and hash table */
|
|
memset(sfx_hashtable, 0, sizeof(sfx_hashtable));
|
|
known_sfx = NULL;
|
|
|
|
for (i = 0; i < MAX_SFX; i++)
|
|
{
|
|
sfx_t *s = &sfx_pool[i];
|
|
snprintf(s->name, sizeof(s->name), "sounds/fx/effect_%04d.ogg", i);
|
|
s->next = known_sfx;
|
|
known_sfx = s;
|
|
|
|
unsigned h = sfx_hash(s->name);
|
|
s->hashnext = sfx_hashtable[h];
|
|
sfx_hashtable[h] = s;
|
|
}
|
|
|
|
/* Correctness */
|
|
for (i = 0; i < MAX_SFX; i++)
|
|
{
|
|
sfx_t *lin = find_sfx_linear(sfx_pool[i].name);
|
|
sfx_t *hsh = find_sfx_hash(sfx_pool[i].name);
|
|
if (lin != hsh)
|
|
{
|
|
printf("FAIL: mismatch at sfx %d: linear=%p hash=%p\n", i, (void*)lin, (void*)hsh);
|
|
pass = 0;
|
|
}
|
|
}
|
|
|
|
/* Not-found */
|
|
if (find_sfx_hash("nonexistent.wav") != NULL)
|
|
{
|
|
printf("FAIL: hash found nonexistent sfx\n");
|
|
pass = 0;
|
|
}
|
|
if (find_sfx_linear("nonexistent.wav") != NULL)
|
|
{
|
|
printf("FAIL: linear found nonexistent sfx\n");
|
|
pass = 0;
|
|
}
|
|
|
|
/* Benchmark */
|
|
start = clock();
|
|
for (i = 0; i < NUM_LOOKUPS; i++)
|
|
{
|
|
int idx = i % MAX_SFX;
|
|
volatile sfx_t *r = find_sfx_linear(sfx_pool[idx].name);
|
|
(void)r;
|
|
}
|
|
end = clock();
|
|
linear_time = (double)(end - start) / CLOCKS_PER_SEC;
|
|
|
|
start = clock();
|
|
for (i = 0; i < NUM_LOOKUPS; i++)
|
|
{
|
|
int idx = i % MAX_SFX;
|
|
volatile sfx_t *r = find_sfx_hash(sfx_pool[idx].name);
|
|
(void)r;
|
|
}
|
|
end = clock();
|
|
hash_time = (double)(end - start) / CLOCKS_PER_SEC;
|
|
|
|
ratio = linear_time / (hash_time > 0 ? hash_time : 0.0001);
|
|
|
|
printf("xonotic-0004: S_FindName linked list scan defect\n");
|
|
printf(" SFX entries: %d, Lookups: %d\n", MAX_SFX, 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;
|
|
}
|