sass / node-sass

:rainbow: Node.js bindings to libsass
https://npmjs.org/package/node-sass
MIT License
8.51k stars 1.32k forks source link

Performance when using javascript custom functions #1198

Open eoneill opened 9 years ago

eoneill commented 9 years ago

Javascript custom functions are awesome, and my current work wouldn't be possible without them, but we're noticing some performance impact when passing along large maps.

I threw together a minimal test case to demonstrate this: eoneill/node-sass-perf-test

The test has two functions (perftest-native and perftest-js) that simply return the value passed to it:

@function perftest-native($value) {
  @return $value;
}
"perftest-js($value)": function($value) {
  return $value;
}

Each method is passed a map with 5000 entries (a bit absurd, granted).

The finding is that the javascript method is exponentially slower than the native (Sass) method:

[native] elapsed time: 2ms
[js] elapsed time: 32ms

Lemme know if I can help provide any more information.

Thanks again for all the awesome work you guys do!

thiakil commented 9 years ago

30ms difference doesn't seem that much? How does this affect in real-world applications?

A JavaScript function will likely never be as efficient thanks to the context switch from C++ -> JS -> C++ and the associated v8 conversions.

(I'm just a curious 3rd party)

xzyfer commented 9 years ago

@thiakil true it's not a show stopper, but it's certainly worth being aware of. It's likely we can improve things here.

I'm not entirely surprised there is degraded performance. For reference the conversion process is

C++ -> C (libsass) -> C (node-sass) -> JS (v8) -|
                                                -> < your function >
                                                       |
                                                       |
C++ <- C (libsass) <- C (node-sass) <- JS (v8)       <-
eoneill commented 9 years ago

Yeah, this isn't so much a complaint as an observation.

For a real world scenario, we use maps to store UI pattern definitions...

@include restyle-define(button, (
  color: blue,
  ...
));

We then retrieve the definitions via another mixin call:

.btn {
  @include restyle(button);
}

We started noticing that as we added more definitions, the compile times started getting exponentially slower, so we did some performance analysis and found the above observation.

We know there are some performance optimizations we can make on our end, but it's rather insignificant compared to the overhead we're seeing here.

For example, we measured the time it took to invoke our javascript call from sass in a realistic scenario and found:

Time in Javascript: 8.113895ms
Time in Sass: 21.282153ms

That is, we spent 8.1ms performing operations in the Javascript function, but it took a total of 21.2 ms to go round-trip. This shows we've incurred a 60% overhead going from sass to javascript, and even if we reduced our JS time to 0ms, we'd still be at ~13ms.

If you image that consumers are making a dozen or so pattern calls per file, this time adds up.

This definitely isn't a show stopper for us and we have some creative ways to work around performance creep on our end.