less / less.js

Less. The dynamic stylesheet language.
http://lesscss.org
Apache License 2.0
17.01k stars 3.41k forks source link

use global regexes (cached) for small speed improvement #2501

Closed lukeapage closed 6 years ago

lukeapage commented 9 years ago

I wonder if this will help with speed given how much we use regex's

http://jsperf.com/regex-caching-lp

SomMeri commented 9 years ago

The parser then decides what to make of that token based on its string value and will compose the full at-rule name, variable name, etc. accordingly.

Doesn't that require semantic predicate e.g. js/java/whatever condition? They were supposed to be used as last resort, since slow down algorithm and can mess up with prediction (from what I remember plus probably depends on what generator you use).

I was doing it that semantic predicates way in less4j (minus last part - generator control that not me) and just finished removing them - moving as much as possible into "pure" grammar.

seven-phases-max commented 9 years ago

A remark not related to the original @lukeapage goal, but to note something important before you go too far along that "parser refactoring" road (as of the recent posts here). Before there were already rumours that for lessc performance in node (and thus less.js performance in a V8 browsers) it's not the parsing stage that is the real bottle-neck but the css generation stage (when compiling some "average" less sources). Here's a lessc compiling bootstrap.less (just for example) V8-profiling snapshot (you can open it in either Blink browser via "developer tools > profiles > Load") for the documental reference. As you can see it's "parsing"/"toCSS" -> ~30%/70% or even worse (the profiling may drift in +/-10% between several runs because of some pretty random GC or so). (The timeline of the profiling data is sort of virtual i.e. 1 sec is not real 1 second there).

In other words if lessc performance is concerned, any improvements are to begin with the CSS generation stage ("ruleset eval", "mixin-lookup" in particular + related stuff, then ruleset and rule visitors etc.) and not with the parser.

rjgotten commented 9 years ago

I'm going to second what @seven-phases-max is stating; the results I can get from a few of my own (quite large) less codebases also indicate that the bottleneck on anything of substantial size and complexity is in the ruleset/mixin eval phase.

I notice there's a lot of array cloning (via slice) and concatenation going on. My guess is you could fix up performance quite a bit by finding a way to eliminate that and using a more proper push/pop stack approach.

And ofcourse; type stability, type stability, type stability. Can't really state that one enough.

matthew-dean commented 9 years ago

Well, let's attack it from all sides, shall we? But in order to do that, we need to be able to quantify improvements: that is, being able to hook in timers (in a dev process) for the time spent in each function.

I think we could do something like have a dev function that monkey patches selective Less functions. That way you wouldn't have any code left in production builds. It could even be a Less plugin. Basically, you override each function with a function of the same name that takes a Performance.now() measurement before and after the original function call, and increment the total count as you go. Output the totals per function at the end, easy peasy.

Once we have that, then we can quantify improvements with compiling different Less stylesheets, at least how it runs in current versions of V8. Otherwise we'd just be guessing if replacing an array slice or concat makes any measurable difference. I know we have total time in our benchmarks, but it would be nice to have function-level benchmarking, so that retooling a specific function is easier to measure.

rjgotten commented 9 years ago

Basically, you override each function with a function of the same name that takes a Performance.now() measurement before and after the original function call, and increment the total count as you go. Output the totals per function at the end, easy peasy.

That's more or less what the JS profiler in both Firefox and Chrome's developer tools are already designed to do.

(Sidenote: Firefox's is also pretty nice in that it supports an 'inverted call tree' setting. It makes it easy to identify the biggest spenders at the lower levels of the call stacks without having to drill down through a huge graph manually.)

There's a NodeJS package that creates bindings to the V8 profiler that's active in Node 0.11, which I think is exactly the kind of telemetry data you're looking for:

https://www.npmjs.com/package/v8-profiler

matthew-dean commented 9 years ago

@rjgotten Oh nice! I'll take a look.

stale[bot] commented 6 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.