Speed up repeated extends by memoizing calculations #2314
Merged
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.
The extend code path is probably the most expensive to go down in LibSass. It involves multiple conversions between the regular AST selector node to the internal used format and back again. Storing results from previous runs is rather cheap, due to the new shared object memory storage and since we already have everything to use selectors efficiently as hash map keys.
This might be more benchmark driven than I'd like, but it's the only way I have to asses our bottlenecks. Btw. I mostly do the performance assessments in Visual Studio Community. Problem is coming up with a good and balanced test case. The extend code regularly pops up as one of the hottest code paths in LibSass. But optimizing this is rather tedious. I started to look into the extend logic, but I don't see any horizon when we can get rid of the wastefull AST to Node conversion.
This speed up basically uses some clever memoizing to optimize the case when you extend the same selectors again. Since the subset_map is gathered in the expand phase before, the results should be the same for all calls with the same selector.
I was not yet able to get this down to the compound selector level as there seem to be some unexpected side effects.My real world test suite has shown a minor increase of 5-10%. While synthetic bencharks can gain up to 500%.So this may not be the ultimate performance boost for extend, but it does give a nice improve for scalability. I expect this to be noticable in very big code bases, where the chance of repeated extends gets higher. The memory costs should be rather slim. We just have two maps that hold on to shared objects that should live on anyway (did not check, but should be the case). Chances are that this should even improve memory usage, as we return the same object for every identical extend. Previousely extend would have created a pretty expensive deep clone. This could have side effects. But if it does I would like to know why, because we do want to avoid unnecessary copies when possible.
Conclusion: The extend code really needs a cleanup 🤔