[track-state] Completely redesign track-state middleware #931
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
I can't believe it took me this long to get to this. The current implementation of
track-state
is very far from ideal and I had problems with it for a long time (see #806). In the current large project I'm testing this, every eval op takes ~300-400ms to recalculate the updated state (even if there were no changes to relevant metadata) and this produces ~250-300 MB of garbage. The state recalculation doesn't delay the evaluation result (as the state update happens on a separate thread), but it delays font lock updates which can be annoying. Besides, the allocated garbage produces extra strain if a REPL process is already low on memory. Finally, the cache used by this middleware occupies again ~200 MB of heap, and there is a counterpart cache on the Emacs side which eats memory and causes GC pauses too.This is a complete redesign. Here are the main points:
:doc
and:arglists
. This instantly reduced the cache size by a lot.{:fn "true"}
(not private, not deprecated, not a macro). Reusing this as a literal brings down retained cached size further. This is very significant.var-symbol -> real-metadata-map
. Because the "relevant metadata map" is always a derivative of a real metadata map, we can skip recomputing the relevant map if the real one hasn't changed, and take the computed map from the cache (from previous run). This map is kept as WeakHashMap to avoid holding onto metadata maps for vars that could have been undefined. The cache WHM is updated if the real metadata of the var has changed since the previous run.The remaining changes are mostly about refactoring and prettyfication. I introduced a couple of dynamic vars to simplify the internal API. Dynvars usually have worse performance, but here it is negligible, and I cache them into local vars where necessary.
Results on that large project:
The public API hasn't changed. There are minor tweaks to be done on CIDER side later, but overall this is backwards-compatible.