Zannick / logic-graph

Tools for video game logic representation and analysis, particularly routing and beatability checks for speedruns and randomizers.
MIT License
3 stars 0 forks source link

Greedy search is too resource-intensive #146

Closed Zannick closed 8 months ago

Zannick commented 8 months ago

We have two greedy-search stages, which both use the same code. The first is the initial greedy search to establish an upper bound--which is usually quickly beaten by the main search. The second is the greedy thread in the main search, which searches for the fastest route to each remaining location as the first one by using a greedy search that removes all other remaining locations.

This stems mainly from the use of expand and first_spot_with_locations_after_actions. expand generates a mapping of every reachable spot with a context object. first_spot_with_locations_after_actions generates a list of contexts to try along with a set of used actions, and this is fairly exponential if it doesn't find something on the first pass.

When we have a specific location goal in mind, we can use A*: The priority queue metric is elapsed time plus min distance to the spot plus the location time. "Min distance" is a challenge, though, due to actions and warps not accounted for. Thankfully, we don't need to measure the metric for the initial context, so we can perform those once and then measure from there--I assert that we can still use those actions and warps afterward.

Here's what we're looking at:

Zannick commented 8 months ago

Replacing move_to with the A version resulted in a 7-8% speedup of the "load_routes" benchmark, which seems promising. Replacing first_spot_with_locations_after_actions with its A version (that allows for any number of actions repeated) results in the "can_win_from_scratch" benchmark hanging indefinitely, whereas it averages 15ms previously.

Potentially, we can reintroduce the limitation on global actions, but it's harder to make use of the shared expand functions if we include them in the heap--possibly we just introduce another map (keyed by Context which could be expensive), or indirect the heap input through provided lambdas...

Zannick commented 8 months ago

Ok, having fixed what was causing the hang, the new search is significantly worse but not broken, hitting well over 8000% worse (nearly 1.4s vs the previous results around 15ms).

Zannick commented 8 months ago

Not really sure what happened with that benchmark result; I have it within a margin of randomness/error on my better computer, at roughly 10ms before and after this last change, for can_win_from_scratch. Unless these are failure results, which would be strange, but those are usually instantaneous (nanoseconds or microseconds).

Zannick commented 8 months ago

Forgot to commit the last changes from the other computer, that's what happened.

Zannick commented 8 months ago

So yes, the new A* brings can_win_from_scratch from 10ms to 1s, and tracking the used global actions actually brings it up further to 10s.

While setting up a profiler, I also discovered this runs out of memory on the larger victory rules. So some improvements are needed.

Zannick commented 8 months ago

For now I think what we'll do is this: revert the use of A-with-actions for the initial greedy search, and change the greedy thread to use the A version for getting to a specific required location. I think we'll need to track depth as well in order to prevent extreme cases.

Zannick commented 8 months ago

Satisfied with this. The greedy thread is a lot faster now as well, having finished around 33 thousand states (searched from to each remaining location) in 371 million total rounds. It may also wind up using a lot of resources, but I've added a few caps to catch excessive usage, and I'll check in with heaptrack to make sure memory isn't overused in this area.