-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
Less 2.x very slow in Safari #2339
Comments
Is there a profiler in safari? I dont have a mac, so we need some help to |
Thanks for looking into this! |
Could it be a case of this? http://www.regular-expressions.info/catastrophic.html I'm no regex expert, but maybe someone who is could take a look at the regexes? |
I tried poking around the Safari debugger. I couldn't track down the same line of code, but when recording timeline events, something certainly is very computationally intensive for Safari, as the browser locked up indefinitely as it tried to record events at a certain point. (It actually made this MacBook Air sluggish for a couple minutes.) |
just curious, what are the cases that the regex (in question) should support? The code example in the code comment only needs: /^("([^"]*)"|'([^']*)')/ But the match groups seem to be collecting more than that (e.g. |
This page also has a regex for this case, but it looks like we're also testing for carriage returns. http://www.metaltoad.com/blog/regex-quoted-string-escapable-quotes Their's is: @jonschlinkert I tested your regex in regex101.com and it doesn't seem to match escaping quotes in the middle of the quoted string, per the comments in the code. |
I would go through the code and see if that regex got changed. If it didnt |
@matthew-dean that uses negative lookbehinds which don't work in javascript, would be awesome if they did. forgot about the escaping when I did the example, this should do it: /^'([^'\\]*\\.)*[^']*'|"([^"\\]*\\.)*[^"]*"/ |
@jonschlinkert Ah, well it's all Greek to me. Since you seem to know some about regex, can you tell why the existing regex might be a problem (if this is indeed the source of the issue)? |
I'm not sure. I haven't looked at the method that's being called yet ( I don't think this would cause any issues, but sometimes when everything is "optional" it can be more processing intensive. e.g. when quantifiers are stars, hmm, I should just look at the method lol |
couple of things that are not really important or solutions, but help to simplify just a little (stuff that is very easily overlooked while refactoring):
still looking... |
not sure, it would take me a while to get up to speed on everything, but it seems that |
Capturing groups can be very expensive; I'd start with reducing those. Infact, you have a number of match groups which do not need to be capturing groups at all when you strip terminator quotes manually, rather than letting the regex handle it. Also, I vaguely remember something about it being bad for regex performance to have a leading optional capturing group. Try having the E.g. str = parserInput.$re( /^(~?)("(?:(?:[^"\\\r\n]|\\.)*)"|'(?:(?:[^'\\\r\n]|\\.)*)')/ );
if ( str ) {
return new( tree.Quoted )( str[ 2 ], str[ 2 ].slice( 1, -1 ), Boolean( str[ 1 ]), index, fileInfo );
} |
@k-lange for the profile you posted, quoted is taking 70ms, but the total time at the start of the post, safari is taking 10x chrome/firefox at 2000ms - so even if quoted was made faster, it must be a general problem, not just one with that specific regex? or was your profiling part of a different test where 70ms is a significant % of the total time? I don't want to spend time micro-fixing a single regex till we know the problem. As I said before, it may be due to the removal of the chunker, making every regex slower (and the quoted one just happens to be the most slow?). Can anyone spread any light on the issue? I don't have a mac to test on. You can re-enable chunking with |
@lukeapage thanks, with the chunker enabled safari is as fast as it was in 1.7.5, so this change indeed caused the performance regression. On the regex: All the screenshots are from one run, the quoted regex appears in different places of the profiling 'tree'. I'm sure I didn't find all occurrences. But these three alone add up to 250ms… |
I was checking this out today using @k-lange's JSFiddle. I think the regular expressions may be a red herring. The reason I say that is that the memory allocation is increasing over time, with frequent garbage collection dumps of a pretty significant size. Take a look at this: As soon as Less begins it works, it (apparently, if I'm reading this correctly) starts allocating resources like crazy, enough so that Chrome is busily garbage collecting chunks of 10MB or more dozens of times in a span of a couple seconds, and the total JS Heap rises pretty quickly. Adding the chunkInput:true seemed to have no effect on this graph. I researched a bit to get a basic understanding of the Timeline profiler. Chrome says that frequent GC and this "stair-step" kind of chart is probably an indication of a memory leak. Or it could be that we're just constructing a massive object that takes double digits of memory to construct and retain. Of note: I did this in Chrome because Safari can never complete profiling. The memory / CPU requirements of profiling Less.js are too great for my MacBook Air, and it ran for 14 minutes without ever finishing. |
On the other hand....... I ran this on Less 1.7.5 and 1.6.3, and there was no change in the graph either. Meaning that my discovery is probably the red herring, and Less has probably always consumed and retained a large amount of memory in the browser. Or if it has a memory leak now, it's probably always had a memory leak. The only way I can see this being relevant is a compounding problem: memory / CPU usage overwhelming Safari at a certain tipping point. |
Possibly related to: #1306 |
I think I may have discovered a possible reason for all the GC at least, but I want to run some tests to make sure. |
+1 for knowing more about that, I tried to take a look at this diff: 1.1.5-extend_patch...v1.3.1 |
I've found some clues, but not nailed down anything that would make a difference. I want to figure out these performance issues, mostly because I want to understand how to solve these with the Web Inspector profiling anyway. But also, solving them may improve Node compile performance, who knows. |
Okay, I've done some more extensive profiling today, looking at heap snapshots in Chrome, and recording multiple timelines in Chrome and Safari, and I think I have an answer. First: in my testing on my system, setting Q: Why is Safari so much slower than Chrome? I know that's an entirely unsatisfying answer. But there was no single code path, or regex that seemed to be significantly adding to the total. A particular piece of the parser may take longer than another (as in @k-lange's example), but not amazingly longer. As expected, the big totals are in parsing the input, but that's most of the work Less does anyway. I believe it's a cumulative effect, and V8 in Chrome is simply faster than JavaScriptCore in Safari, and has optimizations that either JavaScriptCore does not have, or does them better. The logical next question is: why is 1.7.5 faster in Safari? Well, with more functions exposed in the plugin syntax, one possibility is that each segment in parser functions has a little further to travel. So, if you call an additional function 1,000 times, and your JS engine is 1 millisecond slower than another at calling that function (plus all the functions that function calls), then you've added a total of 1 second to parsing. iOS: Same story. And Firefox. V8 is the fastest of the bunch at these particular operations. That's it. So, what are some things V8 might do faster?
Note that these are just educated guesses. There was a lot of data to go through in the respective Inspectors, so I really couldn't say how much any of these things might be affecting the outcome. Now, can we address it? Probably. Maybe. There are a number of techniques for getting objects and arrays to perform better, and to not, say, not work a GC as hard, such as:
Anyway, this is a tough nut to crack, so if anyone else has different data / conclusions, chime in. But as far as I can tell, it's not an easy fix. It's mostly doing performance enhancements here and there across the entire library. |
The earlier posted fiddle calls less.render in a recursive loop. I forked it and made it callable via a clickable button, which is a bit more fit to interactive profiling with the taking of heap snapshots: https://jsfiddle.net/yoe8utbe/6/embedded/result/
This should give you a good view of what actually leaked. And it's really not that much. I had a look at the JavaScript CPU profile and using Firefox's 'invert call stack' functionality, Most CPU time is spent in |
@rjgotten Yes, I did the same thing to isolate it (trigger it on a button). I also found that JSFiddle was not helping because CodeMirror had some memory leaks going. And I agree, the object difference is not that much, so while some leaking may be present, I think it's just overall processing time in each of the JS engines. Those two functions have more time spent because they are logically used most. |
@matthew-dean thanks for the insight about less.js performances! Also, can someone else confirm those tests results on safari? (sorry I'm on linux) |
@kuus My guess is that there is something added to Less which Safari is particularly bad at, or wasn't optimal, but something that Chrome was especially good at optimizing. But yes, I'd love to have other people gather data or run the same tests. It's hard to know without taking individual pieces and running browser tests, but I don't know if that's possible. I'm just learning some of the more advanced Webkit profiling tools, so someone who knows more about it may have more insight. |
I think the main difference feature-wise is that V8 has a generational garbage collector, which I don't believe JavaScriptCore has available yet. That would help immensely when you have a mix of long-lived and extremely short-lived objects. That, and V8 is better in general at handling multiple 'shapes' (type variants) with the optimizing compiler. If you want to increase performance, I'd start by looking at stable types and object pooling. |
I think that's exactly right, based on what I've read about V8. And I agree with those solutions. |
I think you are missing what was confirmed above.
|
The chunkInput setting was confirmed by one person, but I couldn't replicate it. Whether or not chunkInput was enabled had no effect for me in Safari. It makes sense in theory that a regex running on an entire file could be orders of magnitude slower than on a small chunk. I just couldn't replicate it. Has anyone else been able to replicate that result? |
@matthew-dean here's my fiddle from the beginning with |
@k-lange God damn it, you're right. I was setting less options before script load, but not passing them directly into render(). Well, at least I learned a lot about developer tools and JavaScript optimization. Son of a.... Welp, I guess the chunker is a necessity then @lukeapage. |
Just wanted to add that in the current version 2.4.0 it is also very slow in Firefox. |
Also, the main difference I see is that in Chrome, the website does not get rendered at all (white page) until LESS is ready. Then the website shows up correctly all at once. In Firefox, the website already starts to render without the LESS being ready which looks like a website with no CSS at all, and then, after a few seconds, it shows up correctly. This prerendering in FireFox without CSS is troublesome for jQuery |
What about with the chunkInput option enabled? |
Total page load in 8.96s - with or without "var less = { chunkInput: PS: Will there be Dreamweaver CC support for syntax highlighting? www.lookr.com http://www.lookr.com — Take a look around the world Am 12.03.2015 um 19:07 schrieb Matthew Dean:
|
I've experienced severe performance issues in Safari RegExps in a similar context but not as a Less.js user. This may be related because:
See jsperf benchmark that demonstrates the issue. For large enough inputs five!! orders of magnitude performance degradation in safari |
@bd82 The fix for this is to set |
@matthew-dean I've edited my original comment to be more explicit about this to avoid future misunderstandings. |
Is this still relevant ? I wanted to reproduce this on the 3.x branch to attempt a fix. So i've tried modifying the original JSFiddle to use the latest Less version (2.7.1) But I still can't Works well in Safari(9.1) / Chrome / Firefox Am I doing something incorrectly or perhaps this was somehow resolved? |
I tested with the latest less version (2.7.2) on Safari 5.1.7 for Windows with 130kb CSS I can see a significant performance improvement for Safari. 148ms parse time seems to be completely acceptable to me. Don't see much difference on IE 11+, Chrome,FF when chunkInput is toggled. |
@bd82 I'm not sure if this is still relevant. I know that making sure XHR was not sync by default helped things out tremendously. Closing for now. Re-open if there are new Safari issues. |
Version 2.x seems to be about 10x slower in Safari than 1.7.5. I created the following jsfiddles to demonstrate this:
I used the Browserify CDN to get less.js, benchmark.less and ten iterations.
My results (all on OS X Yosemite, 2.4GHz dual core i5):
Some background: I'm using less.js in a webworker for a live, in-browser less-editor. With 2.x it becomes completely unusable in Safari.
I understand that this is probably not a supported use-case, but it would still be great if someone could have a look at this since it probably applies to the distributed browser version as well.
The text was updated successfully, but these errors were encountered: