-
Notifications
You must be signed in to change notification settings - Fork 10
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
Improve speed of parsing #2
Comments
There was a comment on the lessphp repo implying the use of sprintf to concat strings contributed to the slowness? |
I haven't looked deeply at the benchmarks yet, but I think that the main problem is in compiling the If you can help with this I would really appreciate it. |
It definitely seems to be extends. I think there's some kind of combinatorial explosion going on here, but I don't have a couple of days to learn the parser / parse tree. Our LESS is based around bootstrap. Around 3000 braced sections, and around 70 extends, yet somehow it is evaluating 500k matches. But I really can't comment more on that without being an expert. |
I should note that I've been profiling PHP a long time, and the numbers xdebug gives can be somewhat misleading. Function calls are pretty expensive, but the call cost itself doesn't show up directly (you can see it in the grandparent call cumulative cost though). Same goes for basic dereferencing of arrays and objects (either explicitly or in loops). |
Thanks for your effort! The ILess code is direct port of Less.js (started at v1.6), which is now at 2.x version. I haven't looked at what has been changed in the parser code yet. I want to push ILess forward (compat with new less.js features), so this issue is a minor one, but important one. |
I decided to take a closer look at this. The findMatch calls are complex, and called for every selector, for every extend. Let's say each rule block has 3 selectors, there are 1000 rule blocks, and 100 extends... So you see the combinatorial explosion there. I have done an optimisation. I've spend way too much time on debugging this, so I'm afraid I can't make a tidy pull request. Here's a real ugly diff (I couldn't be bothered to set my indent to match, sorry)...
It works on the basis of cacheing a clumped up copy of each ruleset's selectors. E.g. ".foo .bar, .bar .foo" would clump up to "foobarbarfoo" in pathString. The same is done for the extends. If there's a string overlap, we know we need to then look more carefully within findMatch to see if it really does properly match when evaluating all the individual selector expressions properly. My optimisation may not be valid in all cases. I think extend allows multiple selectors, in which case an overlap check would need happening for each one. But it's good enough for me, and I think you can clean it up easily enough. This optimisation got my findMatch calls down from ~500k to ~10k. I think it's improved performance about 60% but I haven't done proper measurements. I believe there's scope for further optimisation. The node structure is swept multiple times, and goes down to the CSS property level, so it's a huge crawl for bootstrap's LESS code. I can see it's been architected to use a relative abstract clean algorithm, but it'd be far more efficient if it just did book-keeping at the parsing stage. At least noting down the extends at parse rather than recursing back through later (ExtendFinder's independent sweep, initiated at the start of ProcessExtend). Doing recursive calls, looping, and constant unpacking arrays many levels deep, is not going to be fast in a dynamic language like php. Maybe ExtendFinder is the only case to reasonably eliminate, so then I'd recommend optimising the recursion, try and pull out some of the intermediary functions in Visitor.php (function calls are fairly expensive). |
I measured the speed boost of the above properly. Compile time reduces by 40%, or 50% if you use trim (with '.# ') instead of preg_replace. |
Chris, many thanks for your effort. The visitor implementation is a direct port of javascript version. I'm slowly working on updates to bring the 2.0 features to Iless and I'm not sure what has changed in the less.js code which I would like to follow. The less implementation may be updated and we can just use the updates done there and keep the code close to each other. The js versions: https://github.com/less/less.js/blob/v1.6.3/lib/less/extend-visitor.js |
A quick look at the master link above suggests to me it would benefit from my optimisation also. I can't see any other fix they've made. I imagine in the JS implementation nobody is crazy enough to do a full bootstrap compile, so they might not have looked at this as closely. |
Is this code going to make it into the repo? |
I've created profile for bootstrap3 compilation. https://blackfire.io/profiles/946e4672-504e-4fac-8dab-869791ccd8a5/graph I will look at this closer. |
Improve speed of parsing
The text was updated successfully, but these errors were encountered: