Skip to content
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

Speed up repeated extends by memoizing calculations #2314

Merged
merged 1 commit into from
Feb 15, 2017

Conversation

mgreter
Copy link
Contributor

@mgreter mgreter commented Feb 4, 2017

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 🤔

@mgreter
Copy link
Contributor Author

mgreter commented Feb 4, 2017

Hmm, that exposes quite a few unexpected side effects. Need to investigate further.

@mgreter mgreter force-pushed the perf/memoize-extend branch from 4c5b3ed to 990eb7f Compare February 4, 2017 10:29
@mgreter
Copy link
Contributor Author

mgreter commented Feb 4, 2017

Interestingly the failures from #2315 in https://ci.appveyor.com/project/sass/libsass/build/1.0.3068/job/ih3by0c12b0fc0jv seem to be the same. I have now deactivated the memoization for complex selectors and that seems to work. This could indicate that some equality tests are not giving the same result as when compared via to_string. Good thing is that it seems to be reproducible via MSVC.

@mgreter mgreter force-pushed the perf/memoize-extend branch 2 times, most recently from 5c22597 to c02a2a5 Compare February 4, 2017 13:16
@mgreter
Copy link
Contributor Author

mgreter commented Feb 4, 2017

OK, got a green light from CI. As expected the (equal and less than) compare functions are not yet 100%. But we need them for our hashing already, so better expose more bugs by (ab)using them.

@mgreter
Copy link
Contributor Author

mgreter commented Feb 4, 2017

And got it also working on the compound selector level. This should bring the most in case the selectors differ slightly. Caching now starts at the best level possible, as all levels have some heavy calculations involved. The cache structure living in the Extend class (which only lives during the extend phase) are very slim. They are really just maps with size_t as key to a shared object (which is a bit more than a pointer). Of course all given that the assumption it is sane to cache these results.

@mgreter
Copy link
Contributor Author

mgreter commented Feb 4, 2017

Well, good thing one does performance tests before making assumptions into facts. It seems the overhead on the compound level is too high. I think the problem might be that we store a "Node" object in the hash map here (since that is the return type). I'll comment this out and add a comment so it can be re-evaluated once we return a true selector from this function.

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.

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 map keys.

It turned out that memoizing Compound-Selectors was too
much overhead. This is probably due to having a `Node`
object a map value (IMO this is a copy assignment). I
commented it out for now with some info for later re-
evaluation when we can store a more efficient value!
@mgreter mgreter force-pushed the perf/memoize-extend branch from 455a103 to ec98942 Compare February 4, 2017 14:17
@mgreter mgreter merged commit d5b6fe7 into sass:master Feb 15, 2017
@mgreter mgreter self-assigned this Mar 30, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant