Bottlenecked on astar allocation and hashing performance
chinedufn opened this issue · comments
All of the below refers to astar, but it may also apply to other functions. I only use astar
at this time.
I'm working on optimizing some pathing code that depends on the pathfinding
crate. Pathfinding is a very hot code path for my application.
As I profile and benchmark my application I'm finding that roughly 50% of the pathing calculation time is being spent allocating memory in hashbrown.
The other roughly 50% of the time is being spent hashing successors when looking them up in the parents IndexMap.
Both of these bottlenecks are addressable, I think.
Non API changing potential improvements
- It looks like the default hasher is being used for the
IndexMap
. This could be sped up by using a faster hasher. This would be at the cost of an extra dependency, however.- Note. The API changing idea below would handle this without any new dependencies on
pathfinding
's side. In my opinion the below approach is better. I'm only suggesting this in case the below approach is controversial for some reason.
- Note. The API changing idea below would handle this without any new dependencies on
API changing potential improvements
The caller of pathfinding's astar will often have a great deal of knowledge about the aspects that impact how much memory is needed to pathfind.
For example, imagine a flat 100x100 grid. When pathfinding, in this example case, a tile's successors are the 8 tiles that surround it.
We know that
- A maximum of 10,000 unique tiles will be visited
- A tile has its 8 adjacent successors (or fewer if it is along the edge of the grid)
Given this information, it is possible to avoid all of the parents
' allocations by simply passing in a map that has a capacity of 10,000.
Also, because we know that we only need a maximum of 10,000 entries, all of the hashing could be avoided as well. For example, each neighbor could be given an identifier between 0-9999 and that can be used to insert / look it up in a pre-allocated vector.
I've explained a simple case, but these types of controls would also exist in more complex cases. Although they might require more code to implement.
Ideally, all of this is of no concern to pathfinding
.
Instead of using the IndexMap
for parents
, astar accepts an &dyn SomeParentHolderTrait
.
Now the caller can hand tailor how parents are looked up, how hashing occurs (if at all), etc.
In short
- allocating
parents
is a performance bottleneck in astar - hashing is a performance bottleneck in astar
- both of these bottlenecks can be removed by allowing the caller to pass in the
parent
s holder
Open Questions
The latter approach would be a breaking change.
Could we potentially introduce a new generic method and have the old astar
call it? Then mark astar
as deprecated noting that it will be replaced with astar_generic
in v3
?
This would allow this to get released without needing to introduce a breaking changing?
Potential Path Forwards
- See what @samueltardieu has to say
- I'd be happy to help with implementing this by:
- First PRing a benchmark(s) so that we know where perf started at
- PRing the API changing potential improvements and seeing how they impact the benchmark(s)
Related Issues
Hi @chinedufn. Your proposal makes sense, and I like the way to go forward you are proposing.
However, why would you want to deprecate the existing function at any point? They can be kept forever as an easy way to use the pathfinding methods without having to deal with a parent holder yourself.
Thanks for reviewing the proposal.
Ah yes that makes more sense. I like it.
Cool. I need this eventually but it isn't pressing at this very moment, so I don't yet know when I will do this.
Could be soon. Could be several months from now. But it's on my (large) checklist.
Thanks!
Hey!
I've decided to just roll my own A*.
Thanks for the help!