Make the JS version faster
The JS-compiled version of Dart Sass hovers around 2.5x and 3x slower than when running on the Dart VM. Some slowdown is inevitable, but this seems extreme. We should put dedicated effort into profiling the JS output and optimizing hot spots.
Common thing with dart2js performance issues is dynamic dispatch. For example, in dynamic s = func(); s.method();. call of method will be compiled to something like J.getInterceptor(s).method$0();, where getInterceptor - is sort-of runtime prototype wrapper that allows to call non-existing methods on JS primitives. Types like List or String are not wrapped in compiled dart code, they are Array and String in JS. So, when you have dynamic variable and call method/property like first, interceptor returns correct prototype for object by doing types checks for String, Array, int and so on.
What that does mean is that usage of dynamic ruins compiled-to-js performance and should be avoided at all costs. Easy workaround is to use Object for variables that could contain objects of different types and doing is checks and calling method on variable of specific types. Using dynamic in VM does not have those performance issues and that's why there is so huge performance gap.
While using Object with type checks may sound like a quick win, better solution would be to refactor code to extract interfaces or something like that so all code will be statically typed without runtime checks.
See this test file as a test case where JS is vastly slower than the Dart VM.
For me, full compilation time went to 2 minutes from 20 seconds
@ogonkov Can you share the stylesheets that are running so much slower?
I'm afraid no, i will try to reproduce the issue, by excluding sass files, but don't know when
This comment also includes a zip file that's reported to have poor JS performance.
Dart Sass 1.13.4 should substantially improve performance for many of the worst cases. I've verified that it speeds up @alex-page's a11ycolor example from about 11s to about 3s on my computer, which puts it within the expected 2-3x slower than the Dart VM. If anyone has any more examples where the JS speed is 4x or more slower than LibSass or the Dart VM, please include them here.
I'm keeping this issue open because I still think there are likely to be code changes we can make that will further improve the JS speed.
Wow thanks for all your hard work @nex3 I will definitely do some experimentation on my end!
Upgrading to [email protected], seems make a trick, compilation time drops to 47s, much better than 1m 40s
I've been doing some light profiling, and it seems that our dash-normalized maps cause a pretty hefty amount of overhead in logic-heavy benchmarks like Susy. We should consider alternatives, such as eagerly normalizing and storing the original names elsewhere or determining them from source spans. We could even go more aggressive and convert intra-file name references to indexes into a pre-allocated list of values, although this would require substantially more up-front complexity.
We could even go more aggressive and convert intra-file name references to indexes into a pre-allocated list of values, although this would require substantially more up-front complexity.
I experimentally implemented this. My implementation was extremely hacky, but I wanted to see whether it would provide a substantial enough improvement to warrant investing in more heavily. I got to the point where about 80% of the variable accesses across the Susy benchmark were being resolved via index rather than via name, and while this did produce a measurable performance improvement, it was only about 1.06x faster on Node and 1.15x faster on the Dart VM.
Notably, this is slower than just replacing dash-normalized maps with normal Dart maps, likely because the latter also improves function and mixin look-up times, while the former is mostly useful for local variables. This leads me to think that index-based variables are not a particularly fertile avenue for further experimentation.
I know angular at some point replaced node-sass with dart-sass (presumably the js version) and I'd say I went from having our builds spend negligible amount of time on scss compilation (honestly a handful of seconds) to at least 2 minutes.
I know you guys have nothing to do with angular side of things, but I'd say its a pretty significant increase in time.
Is there currently any way to increase verbosity of logging to get more of an idea where time is spent in a build? I'm aware of --trace, that seems to just expand stack traces better.
Although improving the build times of the JS output is something we'd like to do, it's unlikely we'll be able to get it to approach Node Sass's build times simply because JS is inherently a substantially slower language than C++. However, we're working on a Node.js host for embedded Dart Sass which will run the natively-compiled Dart code as a subprocess and should substantially speed up compile times. Keep an eye on that repo!
I've played with web assembly and know how you can compile things to webassembly targets and get almost native performance... is that something that could be utilized to achieve near native runtime peformance, without having to rely on distributing native binaries? (Not familiar with how this may or may not be supported with node or npm)
Are there things we can turn on for verbose/debugging output (for sass the npm package)? I'd like to try and identify if we're doing something with our stylesheets that inherently causes massive delays. At this point I'm inclined to move style preprocessing stuff completely out of this project, because of the massive hit while compiling...
I've played with web assembly and know how you can compile things to webassembly targets and get almost native performance... is that something that could be utilized to achieve near native runtime peformance, without having to rely on distributing native binaries? (Not familiar with how this may or may not be supported with node or npm)
Dart has started experimenting with support for WebAssembly. If/when that becomes production-ready, we'll certainly explore it as another possible avenue for improving performance.
Are there things we can turn on for verbose/debugging output (for sass the npm package)? I'd like to try and identify if we're doing something with our stylesheets that inherently causes massive delays. At this point I'm inclined to move style preprocessing stuff completely out of this project, because of the massive hit while compiling...
There's not a flag you can flip, ad I'm not sure exactly what information would be useful there. If you're trying to inspect the performance issues, you're probably better off using a profiling tool of some sort.
Hi, I was wondering if meanwhile we can use the dart vm without JS based app somehow? I attempted to move from node-sass to sass and we went from 2 minutes to 5 minutes of compilation time. (I was actually hoping dart-sass will improve compilation time..) (which is obviously not going to happen anytime soon?) (the issue dates from 2017..) Not to make this political or anything, but if Dart-Sass is supported by Google, arent there resources to dedicate to this issue? I just find it a little hard to believe.
Do you use fibers package @gkatsanos ?
Yes, at least I believe I do. This is a NuxtJS application. Just to be 100% sure I added explicitely an implementation config:
loaders: {
scss: {
sassOptions: {
// eslint-disable-next-line global-require
fiber: require('fibers'),
},
},
},
is there any way to debug this?
I think that should work. Just wonder how much SASS files you have?
I think that should work. Just wonder how much SASS files you have?
Most of our SCSS lives in Vue components and is extracted during build time. I could try to remove somehow all SCSS to see if the build time would dramatically improve but I'd need to find a smart way to do this programmatically. In any case that's a bit off topic, I guess there's a general performance issue here and until there solved returning to node-sass is a one way street
I'm not gonna lie, I was kinda forced into use of dart-sass (as angular-cli uses sass-loader as a dependency, and that was updated to default to 'sass'). At this point, we took such a performance hit... I'm ready to just pull sass/scss out of our project completely. I understand the history and situation behind node-sass, and think long term once the binary stuff is there to work similarly that will ultimately be better... but for now... I guess I am just sticking with plain ol css
@ronnyek I'm sorry to hear that. How much did you performance degrade, and what absolute timings are we talking about?
Apologies about the delay, I spent quite a bit of time trying to get a better idea bout where the time is being spent. It looks like the meat of our time compiling scss is actually compiling elasticui's scss files (https://github.com/elastic/eui/tree/master/src). Before switching to dart-sass, it would literally be negligible... like I wouldn't even notice any time being spent compiling. Afterwards, it was at least a 45sec hit any time I would make changes that had anything to do with elastic's scss.
That being said, I'm no expert with scss, but didn't seem to notice any glaring problems with their scss usage. I didn't see much in the way of import or link or whatever that functionality was that some people reported caused their performance problems.
When you say "anything to do with elastic's scss", what does that mean specifically? Are there mixins or functions you're calling that are causing problems, or is it just loading the library at all?
Digging into this, it looks like the biggest issue is that EUI is implementing its own version of math.pow() (and an nth-root function that doesn't use math.pow()). If I fix those two functions, the compilation time of EUI's light_theme.scss goes from about 20s using the standalone executable to under 2s. I haven't measured the JS version, but I'd expect if anything an even more dramatic speed improvement.
To a degree, this is inevitable: Sass is never going to be a great language for heavy numeric computation, and it doesn't make a lot of sense for us to optimize for that use-case rather than encouraging users to use built-in functions that are much more efficient. That said, I'll take a look at the number implementation and see if there's any low-hanging fruit that can be easily optimized so even the manual pow() implementation gets a bit faster.
That is great information, I really appreciate that you went above and beyond IMO to do even that much investigation. Not that I am anyone with a say, but I agree about the fact that Sass wasn't built to optimize computationally expensive stuff, or that it should be. I'll see if we can make changes manually here that net the same benefits.
Why are you guys digging into a specific library? We run a VueJS app with a bunch of components with SCSS , no framework/library, and still the difference in compilation time was severals tens of percentage points apart.. I fail to see why the discussion diverted that much.
Well it sounds like in this specific case, there are probably things that this library does, that actually amplifies the difference in speed. the dart-sass js library is slower, but it was exceptionally slower because of something this library was doing, and it sounds like fixing this problem in the library actually speeds up sass cli speeds too.
Just for my own curiosity, there must a solid explanation as to why node-sass is so much faster compared to the JS implementation of dart-sass?