Compare commits

...
Sign in to create a new pull request.

7 commits

Author SHA1 Message Date
e882567889 Speed up A* neighbor cache hits
struct sctstr members sct_x, sct_y are normalized, no need to
normalize them again.

The neighbor cache now speeds up distribution path assembly by about
10% without the path cache, and by about 5% with the path cache.
2011-04-12 21:40:08 +02:00
7edcd3ea77 Permit disabling of A* neighbor cache at compile-time
Mostly to measure its effectiveness.  Compile with
AS_NO_NEIGHBOR_CACHE defined to disable it.

The neighbor cache turns out to be useless in my tests: it eats memory
without speeding up the update's distribution path assembly.
2011-04-12 21:40:08 +02:00
a02d3e9fc1 Permit disabling of A* path cache at compile-time
Mostly to measure its effectiveness.  Compile with AS_NO_PATH_CACHE
defined to disable it.

Turns out the path cache is quite effective.  For my continental test
case (Hvy Metal 2 updates), it reduces the number of searches by a
factor of 18.5, speeding up distribution path assembly by a factor of
7.  The price is memory: it uses 135 times more memory than the A*
library.  For my island test case (Hvy Plastic 2 updates), I get 4
times search reduction, 3.5 times faster distribution path assembly,
36 times more memory.
2011-04-12 21:40:08 +02:00
0385c67a8f Fix when best_path() prints A* performance statistics
Print them when A* actually runs, not when best_path() finds a path.
Statistics for unsuccessful runs were lost, and old statistics were
printed for path cache hits.
2011-04-12 21:40:08 +02:00
2797e58c20 A* path and neighbor cache performance statistics
as_clear_cachepath() now prints cache hits, misses, number of entries,
and memory use to stderr, when compiled with AS_STATS defined.
2011-04-12 21:40:08 +02:00
8f92fe40f4 More precise and complete A* performance statistics
Memory usage didn't include path (adp->path), neighbor cache
(adp->neighbor_coords, adp->neighbor_nodes), and the hash table
(adp->hashtab).

While there, print path length.

To get A* statistics on stderr, compile with AS_STATS defined.
2011-04-12 21:40:08 +02:00
c2628d43b7 Clean up A* sector cache leftovers
The sector cache was disabled in v4.2.2, and dropped in commit
8f40f5ad, v4.2.20.  A bit of cache statistics code was left behind.
Remove it.
2011-04-12 21:40:07 +02:00
3 changed files with 93 additions and 17 deletions

View file

@ -58,6 +58,15 @@ static struct as_frompath **fromhead = (struct as_frompath **)0;
static int as_cachepath_on = 0; /* Default to off */ static int as_cachepath_on = 0; /* Default to off */
#ifdef AS_STATS
unsigned as_cache_tries, as_cache_hits;
#define as_cache_try() ((void)as_cache_tries++)
#define as_cache_hit() ((void)as_cache_hits++)
#else
#define as_cache_try() ((void)0)
#define as_cache_hit() ((void)0)
#endif
void void
as_enable_cachepath(void) as_enable_cachepath(void)
{ {
@ -142,6 +151,13 @@ as_clear_cachepath(void)
struct as_frompath *from, *from2; struct as_frompath *from, *from2;
struct as_topath *to, *to2; struct as_topath *to, *to2;
int i, j; int i, j;
#ifdef AS_STATS
size_t index_sz = 0;
unsigned index_nb = 0, paths_nb = 0, paths = 0;
#define stats_index(sz) ((void)(index_sz += (sz), index_nb++))
#else
#define stats_index(sz) ((void)0)
#endif
/* Cache not used yet :) */ /* Cache not used yet :) */
if (fromhead == NULL) if (fromhead == NULL)
@ -153,22 +169,43 @@ as_clear_cachepath(void)
for (to = from->tolist[i]; to; to = to2) { for (to = from->tolist[i]; to; to = to2) {
to2 = to->next; to2 = to->next;
/* Free this path */ /* Free this path */
#ifdef AS_STATS
{
struct as_path *pp;
for (pp = to->path; pp; pp = pp->next)
paths_nb++;
paths++;
}
#endif
as_free_path(to->path); as_free_path(to->path);
/* Free this node */ /* Free this node */
free(to); free(to);
stats_index(sizeof(*to));
} }
} }
/* Now, free the list of lists */ /* Now, free the list of lists */
free(from->tolist); free(from->tolist);
stats_index(WORLD_Y * sizeof(*from->tolist));
/* Save the next pointer */ /* Save the next pointer */
from2 = from->next; from2 = from->next;
/* now, free this from node */ /* now, free this from node */
free(from); free(from);
stats_index(sizeof(*from));
} }
} }
/* Note we don't free the fromhead here, we just zero it. That way, /* Note we don't free the fromhead here, we just zero it. That way,
we can use it next time without mallocing int */ we can use it next time without mallocing int */
memset(fromhead, 0, (sizeof(struct as_frompath *) * WORLD_Y)); memset(fromhead, 0, (sizeof(struct as_frompath *) * WORLD_Y));
stats_index(WORLD_Y * sizeof(*fromhead));
#ifdef AS_STATS
fprintf(stderr, "as_cache %u searches, %u hits, %u entries,"
" index %zu bytes %u blocks, paths %zu bytes %u blocks\n",
as_cache_tries, as_cache_hits,
paths,
index_sz, index_nb,
paths_nb * sizeof(struct as_path), paths_nb);
as_cache_hits = as_cache_tries = 0;
#endif
} }
struct as_path * struct as_path *
@ -177,6 +214,7 @@ as_find_cachepath(coord fx, coord fy, coord tx, coord ty)
struct as_frompath *from; struct as_frompath *from;
struct as_topath *to; struct as_topath *to;
as_cache_try();
/* Is the cache on? if not, return NULL */ /* Is the cache on? if not, return NULL */
if (as_cachepath_on == 0) if (as_cachepath_on == 0)
return NULL; return NULL;
@ -189,8 +227,10 @@ as_find_cachepath(coord fx, coord fy, coord tx, coord ty)
for (from = fromhead[fy]; from; from = from->next) { for (from = fromhead[fy]; from; from = from->next) {
if (from->x == fx) { if (from->x == fx) {
for (to = from->tolist[ty]; to; to = to->next) { for (to = from->tolist[ty]; to; to = to->next) {
if (to->x == tx) if (to->x == tx) {
as_cache_hit();
return to->path; return to->path;
}
} }
} }
} }

View file

@ -31,8 +31,11 @@ as_stats(struct as_data *adp, FILE * fp)
int i; int i;
int j; int j;
int total_q; int total_q;
int total_p;
int total_h; int total_h;
size_t other;
struct as_queue *qp; struct as_queue *qp;
struct as_path *pp;
struct as_hash *hp; struct as_hash *hp;
fprintf(fp, "Statistics:\n"); fprintf(fp, "Statistics:\n");
@ -52,6 +55,10 @@ as_stats(struct as_data *adp, FILE * fp)
i++; i++;
fprintf(fp, "\tsubsumed:\t%d\n", i); fprintf(fp, "\tsubsumed:\t%d\n", i);
total_q += i; total_q += i;
for (i = 0, pp = adp->path; pp; pp = pp->next)
i++;
total_p = i;
fprintf(fp, "path length: %d\n", total_p);
fprintf(fp, "hash table statistics (size %d):\n", adp->hashsize); fprintf(fp, "hash table statistics (size %d):\n", adp->hashsize);
for (i = 0; i < adp->hashsize; i++) { for (i = 0; i < adp->hashsize; i++) {
for (j = 0, hp = adp->hashtab[i]; hp; hp = hp->next) for (j = 0, hp = adp->hashtab[i]; hp; hp = hp->next)
@ -64,10 +71,18 @@ as_stats(struct as_data *adp, FILE * fp)
fprintf(fp, "\tqueues\t%d\n", fprintf(fp, "\tqueues\t%d\n",
(int)(total_q * sizeof(struct as_queue))); (int)(total_q * sizeof(struct as_queue)));
fprintf(fp, "\tnodes\t%d\n", (int)(total_q * sizeof(struct as_node))); fprintf(fp, "\tnodes\t%d\n", (int)(total_q * sizeof(struct as_node)));
fprintf(fp, "\tpath\t%d\n", (int)(total_p * sizeof(struct as_path)));
fprintf(fp, "\thash ents\t%d\n", fprintf(fp, "\thash ents\t%d\n",
(int)(total_h * sizeof(struct as_hash))); (int)(total_h * sizeof(struct as_hash)));
other = sizeof(struct as_data);
other += adp->maxneighbors * sizeof(struct as_coord);
other += (adp->maxneighbors + 1) * sizeof(struct as_node *);
other += adp->hashsize * sizeof(struct as_hash *);
fprintf(fp, "\tother\t%d\n", (int)other);
fprintf(fp, "\ttotal\t%d\n", fprintf(fp, "\ttotal\t%d\n",
(int)(total_q * sizeof(struct as_queue) + (int)(total_q * sizeof(struct as_queue) +
total_q * sizeof(struct as_node) + total_q * sizeof(struct as_node) +
total_h * sizeof(struct as_hash))); total_p * sizeof(struct as_path) +
total_h * sizeof(struct as_hash) +
other));
} }

View file

@ -26,7 +26,6 @@
* --- * ---
* *
* path.c: Empire/A* Interface code. * path.c: Empire/A* Interface code.
* Define AS_STATS for A* statistics.
* *
* Known contributors to this file: * Known contributors to this file:
* Phil Lapsley, 1991 * Phil Lapsley, 1991
@ -35,6 +34,18 @@
* Steve McClure, 1997 * Steve McClure, 1997
*/ */
/*
* Define AS_STATS for A* statistics on stderr.
*
* Define AS_NO_PATH_CACHE to disable the path cache. The path cache
* saves a lot of work, but uses lots of memory. It should be a
* significant net win, unless you run out of memory.
*
* Define AS_NO_NEIGHBOR_CACHE to disable the neighbor cache. The
* neighbor cache trades a modest amount of memory to save a bit of
* work. In its current form, it doesn't really make a difference.
*/
#include <config.h> #include <config.h>
#include <stdio.h> #include <stdio.h>
@ -51,8 +62,6 @@
#define BP_NEIGHBORS 6 /* max number of neighbors */ #define BP_NEIGHBORS 6 /* max number of neighbors */
struct bestp { struct bestp {
int sctcache_hits;
int sctcache_misses;
int bp_mobtype; int bp_mobtype;
struct as_data *adp; struct as_data *adp;
}; };
@ -82,8 +91,10 @@ bp_init(void)
if (bp->adp == NULL) if (bp->adp == NULL)
return NULL; return NULL;
#ifndef AS_NO_NEIGHBOR_CACHE
if (neighsects == NULL) if (neighsects == NULL)
neighsects = calloc(WORLD_SZ() * 6, sizeof(struct sctstr *)); neighsects = calloc(WORLD_SZ() * 6, sizeof(struct sctstr *));
#endif
return bp; return bp;
} }
@ -100,11 +111,16 @@ best_path(struct sctstr *from, struct sctstr *to, char *path, int mob_type)
static struct bestp *mybestpath; static struct bestp *mybestpath;
struct as_data *adp; struct as_data *adp;
struct as_path *ap; struct as_path *ap;
int res;
if (!mybestpath) if (!mybestpath)
mybestpath = bp_init(); mybestpath = bp_init();
adp = mybestpath->adp; adp = mybestpath->adp;
#ifdef AS_NO_PATH_CACHE
ap = NULL;
#else
ap = as_find_cachepath(from->sct_x, from->sct_y, to->sct_x, to->sct_y); ap = as_find_cachepath(from->sct_x, from->sct_y, to->sct_x, to->sct_y);
#endif
if (ap == NULL) { if (ap == NULL) {
adp->from.x = from->sct_x; adp->from.x = from->sct_x;
adp->from.y = from->sct_y; adp->from.y = from->sct_y;
@ -112,22 +128,21 @@ best_path(struct sctstr *from, struct sctstr *to, char *path, int mob_type)
adp->to.y = to->sct_y; adp->to.y = to->sct_y;
mybestpath->bp_mobtype = mob_type; mybestpath->bp_mobtype = mob_type;
if (as_search(adp) < 0) res = as_search(adp);
#ifdef AS_STATS
as_stats(adp, stderr);
#ifndef AS_NO_NEIGHBOR_CACHE
fprintf(stderr, "neighbor cache %zu bytes\n",
WORLD_SZ() * 6 * sizeof(struct sctstr *));
#endif
#endif
if (res < 0)
return -1; return -1;
ap = adp->path; ap = adp->path;
} }
if (bp_path(ap, path) < 0) if (bp_path(ap, path) < 0)
return -1; return -1;
#ifdef AS_STATS
as_stats(adp, stderr);
#endif /* AS_STATS */
#ifdef BP_STATS
fprintf(stderr, "best path %s\n", path);
fprintf(stderr, "cache hits/misses: %d/%d\n",
bp->sctcache_hits, bp->sctcache_misses);
#endif /* BP_STATS */
return 0; return 0;
} }
@ -212,8 +227,8 @@ bp_neighbors(struct as_coord c, struct as_coord *cp, void *pp)
*ssp = sp; *ssp = sp;
} else { } else {
sp = *ssp; sp = *ssp;
sx = XNORM(sp->sct_x); sx = sp->sct_x;
sy = YNORM(sp->sct_y); sy = sp->sct_y;
} }
/* No need to calculate cost each time, just make sure we can /* No need to calculate cost each time, just make sure we can
move through it. We calculate it later. */ move through it. We calculate it later. */
@ -282,19 +297,25 @@ bp_coord_hash(struct as_coord c)
void void
bp_enable_cachepath(void) bp_enable_cachepath(void)
{ {
#ifndef AS_NO_PATH_CACHE
as_enable_cachepath(); as_enable_cachepath();
#endif
} }
void void
bp_disable_cachepath(void) bp_disable_cachepath(void)
{ {
#ifndef AS_NO_PATH_CACHE
as_disable_cachepath(); as_disable_cachepath();
#endif
} }
void void
bp_clear_cachepath(void) bp_clear_cachepath(void)
{ {
#ifndef AS_NO_PATH_CACHE
as_clear_cachepath(); as_clear_cachepath();
#endif
} }
double double