Commit graph

3921 commits

Author SHA1 Message Date
78d7db3baf Merge branch 'pathfind-astar' into pathfind-test 2011-04-12 22:29:40 +02:00
ba08bfc0c9 Generalize new path finder to A*
A* is only usable for a single path, except with a heuristic function
returning always zero, which turns it into Dijkstra's algorithm.
Distribution uses it that way, because it needs to find multiple paths
from the same source as efficiently as possible.  Only the other uses
of path search can profit from A*'s superior efficiency.

I feel the extra complexity is not justified.  Besides, it slows down
distribution path assembly a bit, which is the only case where
efficiency really matters.  Let's stick to Dijkstra's for now.
2011-04-12 22:29:01 +02:00
b340b2d194 Cover sea & air in new path finder's regression test-bed
Make TEST_PATH_FIND cover sea & air paths in addition to land paths.
2011-04-12 22:27:38 +02:00
70ef6066f2 Compile-time option to switch off "multiple paths same source"
Just to facilitate benchmarking.
2011-04-12 22:26:05 +02:00
e9307b7b1d Regression test-bed for new path finder
Define TEST_PATH_FIND to run both the new and the old code, and verify
they yield the same path costs.
2011-04-12 22:25:58 +02:00
d8bef8e806 Merge branch 'pathfind' into pathfind-test
Conflicts:
	src/lib/as/as_cache.c
	src/lib/as/as_stats.c
	src/lib/common/path.c

Modified:
	modified:   include/path.h
	modified:   include/prototypes.h
	modified:   src/lib/update/finish.c
2011-04-12 22:17:21 +02:00
fc0c658646 Merge branch 'old-astar' into pathfind-test 2011-04-12 21:56:08 +02:00
04363a92db Use the new path finder for sea & air, drop bestownedpath()
bestownedpath() is a rather simple-minded breadth-first search.  It's
slower than the new path finder, and maintaining it in addition to the
new path finder makes no sense.
2011-04-12 21:51:31 +02:00
bbd6e9182f Compute distribution paths center by center
This way, we compute all distribution paths from the same center in
one go, and thus fully exploit the fast multiple paths from same
source capability of Dijkstra's algorithm.

Sorting by dist center increases the average length of runs from 4.5
to 73 for my continental test case, and from 3 to 10 for my island
test case.

Compared to the commit before the previous one, distribution path
assembly runs more than 40 times faster for my continental test case,
and more than 5 times faster for my island test case.

The new path finder now runs my continental test case more than 30
times faster than the old A*, and the island test case more than 6
times, in a fraction of the memory.  This makes the continental
updates run 3.5 times faster, and the island updates 6% faster.
Distribution path assembly no longer dominates the continental
update's run time: it takes less than 10% instead of more than 70%.

In a sense, this is the path cache done right.
2011-04-12 21:51:31 +02:00
bf97fa9c9c Exploit fast "multiple paths from same source" in distribution
Dijkstra's algorithm can find multiple paths from the same source.
This is much faster than starting from scratch for every path.

Make distribution path assembly work that way.  This speeds up runs of
distributions to the same center.  The next commit will reorder path
searches to maximize the length of these runs.  It also has benchmark
results.

Allocates four bytes per sector, actually uses only the first 4*n
bytes, where n is the number of distributing sectors.
2011-04-12 21:51:31 +02:00
2fc9dfc526 New path_find_visualize(), to aid debugging 2011-04-12 21:51:31 +02:00
18dd516076 Add performance statistics to path finder
New function path_find_print_stats() prints a few numbers of interest
when compiled with PATH_FIND_STATS defined.
2011-04-12 21:51:31 +02:00
d7dccef3b1 Optimize Dijkstra's inner loop for hex maps
Because the cost to enter a sector is independent of the direction of
entry, we visit sectors at most once.  Exploit that.

Beware: this is not the case for A*.  Pitfall for any future
generalization to A*.

Speeds up distribution path assembly by 35-40% in my tests.
2011-04-12 21:51:31 +02:00
ffbbfcb25f Use the new path finder for land paths, drop old A*
This gets rid of the memory leak mentioned in the previous commit.

To get rid of the buffer overruns for long paths mentioned in the
previous commit, make BestLandPath() fail when path length exceeds
1023 characters.

assemble_dist_paths() and move_ground() pass buffers with a different
size.  Eliminate assemble_dist_paths()'s buffer.  Update now works
regardless of distribution distance (the distribute command still
limits to 1023, to be fixed in a later commit).  Enlarge
move_ground()'s buffers.  Doubles the length of paths accepted by
explore, move, and transport.

I use two test cases to benchmark the path finders: "continental" (Hvy
Metal 2 updates) and "island" (Hvy Plastic 2 updates).

The new path finder runs my tests around 3-4 times faster than the old
A* without its caches.  That's enough to meet its cached performance
for "island", but it's only half as fast for "continental".  Not for
long; big speedups are coming.
2011-04-12 21:48:58 +02:00
90de24d038 New path finder
We've been using Phil Lapsley's A* library to find land paths since
Chainsaw 3.  It's reasonably general, and uses relatively complex data
structures to conserve memory.  Unfortunately, it occasionally leaks a
bit of memory (see commit 86a187c0), and is unsafe for long paths (see
commit e30dc417).

To speed it up, v4.2.2 added two caches: the neighbor cache and the
path cache.

The neighbor cache attempts to speed up lookup of adjacent sectors.
It allocates 6 pointers per sector for that.  In my tests, this is
more, sometimes much more memory than the A* library uses.  See commit
7edcd3ea on branch old-astar for its (modest) performance impact.

The path cache attempts to speed up the update's computation of
distribution path costs.  There, A* runs many times.  Each run finds
many shortest paths, of which only the one asked for is returned.  The
path cache saves all of them, so that when one of them is needed
later, we can get it from the path cache instead of running A* again.
The cache is quite effective, but a bit of a memory hog (see commit
a02d3e9f on branch old-astar).

I'm pretty sure I could speed up the path cache even more by reducing
its excessive memory consumption --- why store paths when we're only
interested in cost?  But that's a bad idea, because the path cache
itself is a bad idea.

Finding many shortest paths from the same source has a well-known
efficient and simple solution: Dijkstra's algorithm[*].

A* is an extension of Dijkstra's algorithm.  It computes a *single*
path faster than Dijkstra's.  But it can't compute *many* shortest
paths from the same source as efficiently as Dijkstra's.

I could try to modify Phil's code to make it compute many shortest
paths from the same source efficiently: turn A* into its special case
Dijkstra's algorithm (at least for distribution path assembly), then
generalize it to the many paths problem.  Of course, I'd also have to
track down its memory allocation bugs, and make it safe for long
paths.

Instead, I'm replacing it.  This commit is the first step: a rather
unsophisticated implementation of Dijkstra's algorithm specialized to
hex maps.  It works with simple data structures: an array for the hex
map (16 bytes per sector), and a binary heap for the priority queue
(16 bytes per sector, most of it never touched).  This is more memory
than Phil's A* uses, but much less than Phil's A* with v4.2.2's
caches.

[*] To fully exploit Dijkstra's "many paths" capability, we need to
compute distribution paths in distribution center order.
2011-04-12 21:44:22 +02:00
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
7e2008e7f4 License upgrade to GPL version 3 or later
Why upgrade?  I'm not a lawyer, but here's my take on the differences
to version 2:

* Software patents: better protection against abuse of patents to
  prevent users from exercising the rights under the GPL.  I doubt
  we'll get hit with a patent suit, but it's a good move just on
  general principles.

* License compatibility: compatible with more free licenses, i.e. can
  "steal" more free software for use in Empire.  I don't expect to steal
  much, but it's nice to have the option.

* Definition of "source code": modernization of some details for today's
  networked world, to make it easier to distribute the software.  Not
  really relevant to us now, as we normally distribute full source code.

* Tivoization: this is about putting GPL-licensed software in hardware,
  then make the hardware refuse to run modified software.  "Neat" trick
  to effectively deny its users their rights under the GPL.  Abuse was
  "pioneered" by TiVo (popular digital video recorders).  GPLv3 forbids
  it.  Unlikely to become a problem for us.

* Internationalization: more careful wording, to harden the license
  outside the US.  The lawyers tell us it better be done that way.

* License violations: friendlier way to deal with license violations.
  This has come out of past experience enforcing the GPL.

* Additional permissions: Probably not relevant to us.

Also include myself in the list of principal authors.
2011-04-12 21:20:58 +02:00
e30dc41717 Document buffer overrun for long land paths
BestLandPath(), BestDistPath() and best_path() are unsafe by design:
they take a path[] argument without a size, and blindly assume there's
enough space.  When that's wrong, bp_path() overruns the caller's
buffer.

move_ground() and assemble_dist_paths() provide space for 512
characters.  best(), dist(), path(), att_reacting_units(), s_commod()
and do_unit_move() provide space for 1024 characters.

A malicious player can arrange paths longer than that, but it takes a
lot of land.

BestAirPath() and BestShipPath() also take a path[] argument without a
size, but they're actually safe: bestownedpath() writes at most 100
(MAXROUTE) characters, perform_mission_bomb() provides space for 512,
sorde(), getpath(), do_unit_move() and nav_ship() for 1024.
2011-04-11 22:29:13 +02:00
5333782046 Remove pointless check for sea from finish_sects()
Checking "sea or unowned" is pointless, because sea is always unowned.
2011-04-11 22:29:13 +02:00
6eecd9cdc8 Optimize assemble_dist_paths() for foreign distribution center
You can't distribute to a foreign sector.  This case is relatively
rare.  However, unsuccessful path search is relatively expensive, and
the extra check doesn't really slow down the common case.
2011-04-11 22:29:13 +02:00
b8002d5603 Optimize dodistribute() for sectors with no distribution center
import_cost is now -1 in that case, so checking that suffices.
2011-04-11 22:29:13 +02:00
f4db4e37b1 Fix assemble_dist_paths()'s recovery from invalid dist center
The recovery avoided crashing here, but left the path costs undefined.
If they happend to be non-negative, dodistribute() still crashed.  Set
the costs to -1 to avoid that.

While there, oops on invalid distribution center.
2011-04-11 22:29:13 +02:00
08cb463878 Speed up export cost calculation in assemble_dist_paths()
Import and export paths enter the same sectors, except for the last
one.  Compute export cost from import cost instead of reverting the
import path.  Do it in dodistribute(), so that we need to store only
import costs.
2011-04-11 22:29:13 +02:00
0095b0c979 Supply charged mobility for backward path
It used the path from supply recipient back to supply source.  Has
always been broken that way.
2011-04-11 22:29:13 +02:00
86a187c04f Document memory leak in as_search() 2011-04-11 22:29:13 +02:00
5962195e9a Make bestpath work for deities in foreign land
Before, it only worked in land owned by the deity.

As always, paths can't cross international borders.
2011-04-11 22:29:13 +02:00
791ba26c5e Merge dodistribute() parameters dist_i_cost, dist_e_cost
Only one of them is used, depending on argument imex.  Replace them by
a single parameter path_cost.
2011-04-11 22:29:12 +02:00
d1bdeb4353 Remove dodistribute() parameter path
It was only used to see whether a path to the dist center exists.  Use
negative cost for that.
2011-04-11 22:29:12 +02:00
99c73f399a SAVE_FINISH_PATHS hasn't been used since 4.2.2, remove it
Since 4.2.2, assemble_dist_paths() stores a dummy path instead of the
real path to the dist center.  That's possible because distribution
doesn't actually use the path, only whether it exists.

The code to store and free the real path is still around, under #ifdef
SAVE_FINISH_PATHS.  Remove it.
2011-04-11 22:29:12 +02:00
25665bc49a Print distribution costs when compiled with DISTRIBUTE_DEBUG 2011-04-11 22:29:12 +02:00
4547d3dcc2 Collect path-related stuff in path.h 2011-04-11 22:29:12 +02:00
9fee5efe57 Log distribution path assembly's CPU use (user and system time) 2011-04-11 22:29:12 +02:00
3dafd404fa Log update's CPU use (user and system time) 2011-04-11 22:29:12 +02:00
Ron Koenderink
154bb241f0 Fleshed out getrusage() stub for Windows 2011-04-11 22:29:12 +02:00
8ab0bca0c6 Provide a getrusage() stub for Windows 2011-04-11 22:29:12 +02:00
64e58e3417 Make savecore mind available disk space 2011-04-11 22:29:12 +02:00
30f12acf73 Make savecore check core file is accessible 2011-04-11 22:29:12 +02:00
88983a1a2e Fix bitmap overruns when WORLD_X * WORLD_Y not a multiple of 16
World-sized bitmaps were allocated with size WORLD_SZ() / 8, which
expands to (WORLD_X * WORLD_Y / 2) / 8.  The divisions truncate unless
WORLD_X * WORLD_Y is a multiple of 16.  The bitmaps were one byte too
small then.  Bitmap overruns happen when:

* A lookout looks at one of the last sectors of the sector file.
  Besides commands look and llook, this affects navigate and march
  sub-command 'l'.

* Command spy spies into one of the last sectors of the sector file.

* A map or nmap (but not a bmap) shows one of the last sectors of the
  sector file, or a sector that can see one of the last sectors
  (visual range is two sectors at 100% efficiency).  Besides commands
  lmap, map, nmap, pmap, smap, this affects move and transport
  sub-command 'm'.

Diagnosed with valgrind.

Already broken in BSD Empire 1.1 (bitmaps were on the stack then).
2011-04-11 22:29:12 +02:00
6c9363cc4f Fix pathrange()'s computation of the range's right limit
Because of the bug, the path command's maps weren't always fitted to
the path correctly.  Broken in commit 0f458d2c, v4.3.17
2011-03-28 20:28:36 +02:00
fe372539b2 Land units no longer hit allied mines 2011-02-18 18:46:05 +01:00
67b9135e96 Use relations_with() in sendmessage()
We know player != other.  Because we can have only one player in state
PS_PLAYING per country, and we know other->state == PS_PLAYING, it
follows that player->cnum != other->cnum.  Thus, no functional change.

Adds another call to getnatp() hidden in relations_with(), though.
Keeping that optimized isn't worth it.
2011-02-18 18:46:05 +01:00
6852ec6bc5 Use relations_with() for getrel(NP, THEM) where NP isn't THEM
Replacing getrel(NP, THEM), where NP is known to be getnatp(US), by
relations_with(US, THEM) makes a difference only when US equals THEM.
Replace in places where it's obvious that they're not equal.

Adds a few calls to getnatp() hidden in relations_with().  Keeping
that optimized isn't worth it.
2011-02-18 18:46:05 +01:00
bdf63bc5fa Use relations_with() for US==THEM || getrel(NP, THEM)
Replace patterns like "US == THEM || getrel(NP, THEM)...", where NP is
known to be getnatp(US), by "relations_with(US, THEM)...".  No
functional change.

Adds a few calls to getnatp() hidden in relations_with(), though.
Keeping that optimized isn't worth it.
2011-02-18 18:46:05 +01:00
928e9a4cc3 Use relations_with() in unit_interdict()
No functional change, because the value of rel only matters when cn !=
victim, and then it's the same as before.

The new value of rel permits simplifying cn != victim && rel <=
NEUTRAL to just rel <= NEUTRAL
2011-02-18 18:46:04 +01:00