-
-
Notifications
You must be signed in to change notification settings - Fork 126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Performance] Improve the performance of Radius.tsx by 2x #48
Conversation
All current tests pass but let me know if you want me to add a couple more to make sure |
athena/Radius.tsx
Outdated
const key = vector.toJSON(); | ||
let accessible = cacheMap.get(key); | ||
if (accessible != null) { | ||
return accessible; | ||
} | ||
accessible = isAccessibleBase(map, unit, vector); | ||
cacheMap.set(key, accessible); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two notes on this:
vectors
are immutable objects that are all the same, so you can use them directly as keys for maps without stringify-ing them.- We cannot cache at the module level a map (
MapData
instance) changes after mutations, or we might check the movement radius of different maps.
We need one map per MapData
instance – and then the question is how we evict the memory since it might be running in a long running process on the client or server.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and then the question is how we evict the memory since it might be running in a long running process on the client or server.
First thing that comes to mind is an LRU cache for each map instance in this case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would assume we could use a WeakMap?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As in, a WeakMap<MapData, Map<Vector, boolean>>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would assume we could use a WeakMap?
It's viable, although I suggest a more robust library. Let me know what you think about these latest changes
That's awesome, thank you for the PR! I left a few comments to make sure we aren't breaking something across maps by caching data globally. Could you also share the benchmark you used? |
I used the benchmarkify library with the mock data from the first test in (Radius.test.tsx) |
That's great. Can you also share the code so we can repro it? |
Absolutely, https://github.com/rortan134/athena-crisis/blob/radius-perf-bmark/athena/__tests__/RadiusBenchmark.test.tsx |
Thanks for the updates! I realized that we can actually avoid all of the extra complexity and don't need to add an LRUCache. The radius cache is specific to a MapData instance, and MapData is immutable. It's just a cache to speed things up, so how about we just add the cache interface to However, this led me to uncover a gap in this optimization. |
I see.
A couple test cases would be great |
getTileInfo(vector: Vector, layer?: TileLayer, index?: number) { | ||
if (!this.contains(vector)) { | ||
throw new Error( | ||
`getTileInfo: Vector '${vector.x},${vector.y}' is not within the map limits of width '${this.size.width}' and height '${this.size.height}'.`, | ||
); | ||
} | ||
|
||
return getTileInfo(this.map[this.getTileIndex(vector)], layer); | ||
return getTileInfo(this.map[index ?? this.getTileIndex(vector)], layer); | ||
} | ||
|
||
maybeGetTileInfo(vector: Vector, layer?: TileLayer) { | ||
maybeGetTileInfo(vector: Vector, layer?: TileLayer, index?: number) { | ||
if (this.contains(vector)) { | ||
return getTileInfo(this.map[this.getTileIndex(vector)], layer); | ||
return getTileInfo(this.map[index ?? this.getTileIndex(vector)], layer); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of changing these functions, let's add getTileInfoByIndex
and maybeGetTileInfoByIndex
functions.
getCache() { | ||
return this.unitAccessibilityCache; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getMutableUnitAccessibilityCache
@@ -179,6 +179,7 @@ export default class MapData { | |||
private _hasNeutralUnits: boolean | null = null; | |||
private _activeUnitTypes: ReadonlyMap<PlayerID, ActiveUnitTypes> | null = | |||
null; | |||
protected unitAccessibilityCache: Map<string | number, boolean>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, as shared earlier, we can just use Vector
directly as keys. vec(1, 1) === vec(1, 1)
.
Thanks for the updates, I think we are getting closer to something that can be merged. As for the benchmark, it's not super representative to run it on the same inputs – if that was a common case we could just cache the outputs of the function completely! I added one new test on main that will fail on this branch. Here is a more representative benchmark that calculates the radius for each unit on the map:
It might be worth randomizing the order at the beginning of the run, but this should be a good start. |
I added the benchmarks right to the repo using Feel free to extend them as well. |
@rortan134 are you planning on getting back to this? |
Closing due to inactivity. Happy to reopen if it's being picked up again. |
Closes #9
I used the
moveable
function as a benchmark entry point so other results may vary.Consistently achieved just a bit over a 2x improvement with few to no core changes.
Platform
Windows_NT 10.0.19045 x64
Node.JS: 20.12.2
V8: 11.3.244.8-node.19