Skip to content

Commit

Permalink
Merge pull request #760 from sass/js-perf
Browse files Browse the repository at this point in the history
Improve the JS performance for logic-heavy stylesheets
  • Loading branch information
nex3 committed Jul 15, 2019
2 parents 0e47e03 + 535030b commit f2bc521
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 77 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,9 @@
## 1.22.5

### JavaScript API

* Improve performance for logic-heavy stylesheets.

## 1.22.4

* Fix a bug where at-rules imported from within a style rule would appear within
Expand Down
12 changes: 10 additions & 2 deletions lib/src/utils.dart
Expand Up @@ -306,7 +306,11 @@ bool startsWithIgnoreSeparator(String string, String prefix) {
/// If [source] is passed, copies it into the map.
Map<String, V> normalizedMap<V>([Map<String, V> source]) {
var map = LinkedHashMap<String, V>(
equals: equalsIgnoreSeparator, hashCode: hashCodeIgnoreSeparator);
// Explicitly set this because the default implementation involves a type
// check, which is very expensive in dart2js.
isValidKey: (_) => true,
equals: equalsIgnoreSeparator,
hashCode: hashCodeIgnoreSeparator);
if (source != null) map.addAll(source);
return map;
}
Expand All @@ -316,7 +320,11 @@ Map<String, V> normalizedMap<V>([Map<String, V> source]) {
/// If [source] is passed, copies it into the set.
Set<String> normalizedSet([Iterable<String> source]) {
var set = LinkedHashSet(
equals: equalsIgnoreSeparator, hashCode: hashCodeIgnoreSeparator);
// Explicitly set this because the default implementation involves a type
// check, which is very expensive in dart2js.
isValidKey: (_) => true,
equals: equalsIgnoreSeparator,
hashCode: hashCodeIgnoreSeparator);
if (source != null) set.addAll(source);
return set;
}
Expand Down
14 changes: 9 additions & 5 deletions lib/src/visitor/async_evaluate.dart
Expand Up @@ -1962,15 +1962,19 @@ class _EvaluateVisitor
{bool trackSpans}) async {
trackSpans ??= _sourceMap;

var positional = (await mapAsync(arguments.positional,
(Expression expression) => expression.accept(this)))
.toList();
var positional = [
for (var expression in arguments.positional) await expression.accept(this)
];
var named = await normalizedMapMapAsync<String, Expression, Value>(
arguments.named,
value: (_, expression) => expression.accept(this));

var positionalNodes =
trackSpans ? arguments.positional.map(_expressionNode).toList() : null;
var positionalNodes = trackSpans
? [
for (var expression in arguments.positional)
_expressionNode(expression)
]
: null;
var namedNodes = trackSpans
? mapMap<String, Expression, String, AstNode>(arguments.named,
value: (_, expression) => _expressionNode(expression))
Expand Down
16 changes: 10 additions & 6 deletions lib/src/visitor/evaluate.dart
Expand Up @@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_evaluate.dart.
// See tool/grind/synchronize.dart for details.
//
// Checksum: cae91fd0fcd94fbb45712286a85761d69fef5415
// Checksum: 1685f0e62002cbb13d3d54ea3d5166c4c7f8f678
//
// ignore_for_file: unused_import

Expand Down Expand Up @@ -1949,14 +1949,18 @@ class _EvaluateVisitor
{bool trackSpans}) {
trackSpans ??= _sourceMap;

var positional = arguments.positional
.map((Expression expression) => expression.accept(this))
.toList();
var positional = [
for (var expression in arguments.positional) expression.accept(this)
];
var named = normalizedMapMap<String, Expression, Value>(arguments.named,
value: (_, expression) => expression.accept(this));

var positionalNodes =
trackSpans ? arguments.positional.map(_expressionNode).toList() : null;
var positionalNodes = trackSpans
? [
for (var expression in arguments.positional)
_expressionNode(expression)
]
: null;
var namedNodes = trackSpans
? mapMap<String, Expression, String, AstNode>(arguments.named,
value: (_, expression) => _expressionNode(expression))
Expand Down
125 changes: 63 additions & 62 deletions perf.md
Expand Up @@ -4,7 +4,7 @@ the benefit Dart Sass could provide relative to other implementations.
This was tested against:

* libsass 0f3d6ad1 and sassc 4674821 compiled with g++ (Debian 7.3.0-18) 7.3.0.
* Dart Sass 088fc28 on Dart 2.4.0 and Node v12.0.0.
* Dart Sass 50a45a7 on Dart 2.4.0 and Node v12.0.0.
* Ruby Sass 8d1edc76 on ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux].

on Debian x64 with Intel Xeon E5-1650 v3 @ 3.50GHz. The Dart Sass
Expand All @@ -20,148 +20,149 @@ I ran five instances of each configuration and recorded the fastest time.

Running on a file containing 4 instances of `.foo {a: b}`:

* sassc: 0.004s
* Dart Sass from a script snapshot: 0.219s
* Dart Sass native executable: 0.020s
* Dart Sass on Node.js: 0.200s
* Ruby Sass with a hot cache: 0.155s
* sassc: 0.005s
* Dart Sass from a script snapshot: 0.216s
* Dart Sass native executable: 0.018s
* Dart Sass on Node.js: 0.209s
* Ruby Sass with a hot cache: 0.148s

Based on these numbers, Dart Sass from a native executable is approximately:

* 5.0x slower than libsass
* 10.0x faster than Dart Sass on Node
* 7.8x faster than Ruby Sass
* 3.6x slower than libsass
* 11.6x faster than Dart Sass on Node
* 8.2x faster than Ruby Sass

## Large Plain CSS

Running on a file containing 2^17 instances of `.foo {a: b}`:

* sassc: 1.714s
* Dart Sass from a script snapshot: 1.606s
* Dart Sass native executable: 1.547s
* Dart Sass on Node.js: 2.672s
* Ruby Sass with a hot cache: 11.145s
* sassc: 1.705s
* Dart Sass from a script snapshot: 1.550s
* Dart Sass native executable: 1.518s
* Dart Sass on Node.js: 2.765s
* Ruby Sass with a hot cache: 10.965s

Based on these numbers, Dart Sass from a native executable is approximately:

* 1.1x faster than libsass
* 1.7x faster than Dart Sass on Node
* 1.8x faster than Dart Sass on Node
* 7.2x faster than Ruby Sass

## Preceding Sparse `@extend`

Running on a file containing `.x {@extend .y}`, 2^17 instances of `.foo {a: b}`, and then `.y {a: b}`:

* sassc: 1.750s
* Dart Sass from a script snapshot: 1.602s
* Dart Sass native executable: 1.585s
* Dart Sass on Node.js: 2.782s
* Ruby Sass with a hot cache: 17.012s
* sassc: 1.712s
* Dart Sass from a script snapshot: 1.613s
* Dart Sass native executable: 1.582s
* Dart Sass on Node.js: 2.739s
* Ruby Sass with a hot cache: 16.472s

Based on these numbers, Dart Sass from a native executable is approximately:

* 1.1x faster than libsass
* 1.8x faster than Dart Sass on Node
* 10.7x faster than Ruby Sass
* 1.7x faster than Dart Sass on Node
* 10.4x faster than Ruby Sass

## Following Sparse `@extend`

Running on a file containing `.y {a: b}`, 2^17 instances of `.foo {a: b}`, and then `.x {@extend .y}`:

* sassc: 1.724s
* Dart Sass from a script snapshot: 1.610s
* Dart Sass native executable: 1.568s
* Dart Sass on Node.js: 2.712s
* Ruby Sass with a hot cache: 16.670s
* sassc: 1.701s
* Dart Sass from a script snapshot: 1.568s
* Dart Sass native executable: 1.543s
* Dart Sass on Node.js: 2.821s
* Ruby Sass with a hot cache: 16.469s

Based on these numbers, Dart Sass from a native executable is approximately:

* 1.1x faster than libsass
* 1.7x faster than Dart Sass on Node
* 10.6x faster than Ruby Sass
* 1.8x faster than Dart Sass on Node
* 10.7x faster than Ruby Sass

## Preceding Dense `@extend`

Running on a file containing `.bar {@extend .foo}` followed by 2^17 instances of `.foo {a: b}`:

* sassc: 2.290s
* Dart Sass from a script snapshot: 2.476s
* Dart Sass native executable: 2.566s
* Dart Sass on Node.js: 5.399s
* Ruby Sass with a hot cache: 29.002s
* sassc: 2.260s
* Dart Sass from a script snapshot: 2.405s
* Dart Sass native executable: 2.526s
* Dart Sass on Node.js: 5.612s
* Ruby Sass with a hot cache: 28.690s

Based on these numbers, Dart Sass from a native executable is approximately:

* 1.1x slower than libsass
* 2.1x faster than Dart Sass on Node
* 11.3x faster than Ruby Sass
* 2.2x faster than Dart Sass on Node
* 11.4x faster than Ruby Sass

## Following Dense `@extend`

Running on a file containing 2^17 instances of `.foo {a: b}` followed by `.bar {@extend .foo}`:

* sassc: 2.317s
* Dart Sass from a script snapshot: 2.381s
* Dart Sass native executable: 2.461s
* Dart Sass on Node.js: 5.481s
* Ruby Sass with a hot cache: 29.197s
* sassc: 2.289s
* Dart Sass from a script snapshot: 2.396s
* Dart Sass native executable: 2.457s
* Dart Sass on Node.js: 6.319s
* Ruby Sass with a hot cache: 28.708s

Based on these numbers, Dart Sass from a native executable is approximately:

* 1.1x slower than libsass
* 2.2x faster than Dart Sass on Node
* 11.9x faster than Ruby Sass
* 2.6x faster than Dart Sass on Node
* 11.7x faster than Ruby Sass

## Bootstrap

Running on a file containing 16 instances of importing the Bootstrap framework:

* sassc: 0.763s
* Dart Sass from a script snapshot: 1.613s
* Dart Sass native executable: 0.992s
* Dart Sass on Node.js: 3.529s
* Ruby Sass with a hot cache: 12.969s
* sassc: 0.767s
* Dart Sass from a script snapshot: 1.534s
* Dart Sass native executable: 0.955s
* Dart Sass on Node.js: 3.156s
* Ruby Sass with a hot cache: 12.521s

Based on these numbers, Dart Sass from a native executable is approximately:

* 1.3x slower than libsass
* 3.6x faster than Dart Sass on Node
* 1.2x slower than libsass
* 3.3x faster than Dart Sass on Node
* 13.1x faster than Ruby Sass

## a11ycolor

Running on a file containing test cases for a computation-intensive color-processing library:

* sassc: 0.262s
* Dart Sass from a script snapshot: 0.805s
* Dart Sass native executable: 0.612s
* Dart Sass on Node.js: 1.876s
* Ruby Sass with a hot cache: 5.396s
* sassc: 0.248s
* Dart Sass from a script snapshot: 0.736s
* Dart Sass native executable: 0.565s
* Dart Sass on Node.js: 1.043s
* Ruby Sass with a hot cache: 5.091s

Based on these numbers, Dart Sass from a native executable is approximately:

* 2.3x slower than libsass
* 3.1x faster than Dart Sass on Node
* 8.8x faster than Ruby Sass
* 1.8x faster than Dart Sass on Node
* 9.0x faster than Ruby Sass

## Susy

Running on a file containing test cases for the computation-intensive Susy grid framework:

* sassc: 0.244s
* sassc: 0.248s
* Dart Sass from a script snapshot: 0.673s
* Dart Sass native executable: 0.248s
* Dart Sass on Node.js: 1.361s
* Ruby Sass with a hot cache: 1.576s
* Dart Sass native executable: 0.237s
* Dart Sass on Node.js: 0.990s
* Ruby Sass with a hot cache: 1.527s

Based on these numbers, Dart Sass from a native executable is approximately:

* identical to libsass
* 5.5x faster than Dart Sass on Node
* 4.2x faster than Dart Sass on Node
* 6.4x faster than Ruby Sass

# Prior Measurements

* [1.22.4](https://github.com/sass/dart-sass/blob/a7172a2b1dd48b339e5d57159ed364ffb9f5812e/perf.md).
* [1.20.2](https://github.com/sass/dart-sass/blob/4b7699291c9f69533d25980d23b0647266b665f2/perf.md).
* [1.13.4](https://github.com/sass/dart-sass/blob/b6ccc91a138e75420227ff79381c5f70e60254f1/perf.md).
* [1.6.0](https://github.com/sass/dart-sass/blob/048cbe197a77e1cf4b837a40a5acb737e949fd5c/perf.md).
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
@@ -1,5 +1,5 @@
name: sass
version: 1.22.4
version: 1.22.5
description: A Sass implementation in Dart.
author: Dart Team <misc@dartlang.org>
homepage: https://github.com/sass/dart-sass
Expand Down
5 changes: 4 additions & 1 deletion tool/grind/npm.dart
Expand Up @@ -40,7 +40,10 @@ void _js({@required bool release}) {
// * We expect our test coverage to ensure that nothing throws subtypes of
// Error.
// * We thoroughly test edge cases in user input.
if (release) ...["-O4", "--fast-startup"]
//
// We don't minify because download size isn't especially important
// server-side and it's nice to get readable stack traces from bug reports.
if (release) ...["-O4", "--no-minify", "--fast-startup"]
]);
var text = destination
.readAsStringSync()
Expand Down

0 comments on commit f2bc521

Please sign in to comment.