From 0ce65fc796bdf5201b410d211de18f00877cdd2c Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 15 Oct 2021 18:07:42 -0700 Subject: [PATCH 1/6] Replace Susy with Carbon as a benchmark target Neither of these support Node v16 yet, but Susy is unmaintained and will never support it while Carbon will likely add support in the near future. --- lib/src/io/node.dart | 3 +-- tool/grind/benchmark.dart | 57 ++++++++++++++++++++++++--------------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/lib/src/io/node.dart b/lib/src/io/node.dart index 680b950f..12e24d5a 100644 --- a/lib/src/io/node.dart +++ b/lib/src/io/node.dart @@ -183,8 +183,7 @@ T _systemErrorToFileSystemException(T callback()) { return callback(); } catch (error) { if (error is! JsSystemError) rethrow; - throw FileSystemException._( - _cleanErrorMessage(error), error.path); + throw FileSystemException._(_cleanErrorMessage(error), error.path); } } diff --git a/tool/grind/benchmark.dart b/tool/grind/benchmark.dart index 912d2de6..ca50bca5 100644 --- a/tool/grind/benchmark.dart +++ b/tool/grind/benchmark.dart @@ -55,14 +55,20 @@ Future benchmarkGenerate() async { } """); - var susy = await cloneOrCheckout("https://github.com/oddbird/susy", "v3.0.5"); - await runAsync("npm", arguments: ["install"], workingDirectory: susy); - File("${sources.path}/susy.scss") - .writeAsStringSync("@import '../susy/test/scss/test.scss'"); - await cloneOrCheckout("https://github.com/zaydek/duomo", "v0.7.12"); File("${sources.path}/duomo.scss") .writeAsStringSync("@import '../duomo/scripts/duomo.scss'"); + + // Note: This version only supports Node Sass 5.x, which only supports up to + // Node 14.x. Once there's a version that support Node Sass 6.x, we should use + // that instead. + var carbon = await cloneOrCheckout( + "https://github.com/carbon-design-system/ibm-cloud-cognitive", + "@carbon/ibm-cloud-cognitive@0.93.2"); + await runAsync("npm", arguments: ["install"], workingDirectory: carbon); + File("${sources.path}/carbon.scss") + .writeAsStringSync("@import '../ibm-cloud-cognitive/packages/" + "cloud-cognitive/src/index-without-carbon-released-only'"); } /// Writes [times] instances of [text] to [path]. @@ -96,9 +102,8 @@ Future _writeNTimes(String path, String text, num times, "pkg-npm-release") Future benchmark() async { var libsass = - await cloneOrCheckout('https://github.com/sass/libsass', 'origin/master'); - var sassc = - await cloneOrCheckout('https://github.com/sass/sassc', 'origin/master'); + await cloneOrCheckout('https://github.com/sass/libsass', 'master'); + var sassc = await cloneOrCheckout('https://github.com/sass/sassc', 'master'); await runAsync("make", runOptions: RunOptions( @@ -161,15 +166,18 @@ I ran five instances of each configuration and recorded the fastest time. "a11ycolor", "test cases for a computation-intensive color-processing library" ], - [ - "susy.scss", - "Susy", - "test cases for the computation-intensive Susy grid framework" - ], [ "duomo.scss", "Duomo", - "the output of the numerically-intensive Duomo framework" + "the output of the numerically-intensive Duomo framework " + "(skipping LibSass due to module system use)" + ], + [ + "carbon.scss", + "Carbon", + "the output of the import-intensive Carbon framework", + "-I", + "build/ibm-cloud-cognitive/node_modules" ], ]; @@ -179,6 +187,7 @@ I ran five instances of each configuration and recorded the fastest time. var path = p.join('build/benchmark', info[0]); var title = info[1]; var description = info[2]; + var extraArgs = info.sublist(3); buffer.writeln("## $title"); buffer.writeln(); @@ -187,23 +196,27 @@ I ran five instances of each configuration and recorded the fastest time. Duration? sasscTime; if (!libsassIncompatible.contains(info[1])) { - sasscTime = await _benchmark(p.join(sassc, 'bin', 'sassc'), [path]); + sasscTime = + await _benchmark(p.join(sassc, 'bin', 'sassc'), [path, ...extraArgs]); buffer.writeln("* sassc: ${_formatTime(sasscTime)}"); } - var scriptSnapshotTime = await _benchmark(Platform.executable, - ['--no-enable-asserts', p.join('build', 'sass.snapshot'), path]); + var scriptSnapshotTime = await _benchmark(Platform.executable, [ + '--no-enable-asserts', + p.join('build', 'sass.snapshot'), + path, + ...extraArgs + ]); buffer.writeln("* Dart Sass from a script snapshot: " "${_formatTime(scriptSnapshotTime)}"); - var nativeExecutableTime = await _benchmark( - p.join(sdkDir.path, 'bin/dartaotruntime'), - [p.join('build', 'sass.native'), path]); + var nativeExecutableTime = + await _benchmark(p.join('build', 'sass.native'), [path, ...extraArgs]); buffer.writeln("* Dart Sass native executable: " "${_formatTime(nativeExecutableTime)}"); - var nodeTime = - await _benchmark("node", [p.join('build', 'npm', 'sass.js'), path]); + var nodeTime = await _benchmark( + "node", [p.join('build', 'npm', 'sass.js'), path, ...extraArgs]); buffer.writeln("* Dart Sass on Node.js: ${_formatTime(nodeTime)}"); buffer.writeln(); From fa2576449e78692688a747d4ea1d7fa9f273f9eb Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 15 Oct 2021 18:25:09 -0700 Subject: [PATCH 2/6] Update benchmarks --- perf.md | 132 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/perf.md b/perf.md index 07a0c3e7..469083a1 100644 --- a/perf.md +++ b/perf.md @@ -3,8 +3,8 @@ the benefit Dart Sass could provide relative to other implementations. This was tested against: -* libsass d4d74ef5 and sassc 66f0ef3 compiled with g++ (Debian 10.2.0-16) 10.2.0. -* Dart Sass ae967c7 on Dart 2.10.4 (stable) (Wed Nov 11 13:35:58 2020 +0100) on "linux_x64" and Node v14.7.0. +* libsass da91d985 and sassc 66f0ef3 compiled with g++ (Debian 10.3.0-11) 10.3.0. +* Dart Sass 7934ad9 on Dart 2.14.1 (stable) (Wed Sep 8 13:33:08 2021 +0200) on "linux_x64" and Node v16.10.0. on Debian x64 with Intel Core i7-8650U CPU @ 1.90GHz. @@ -16,139 +16,139 @@ 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.002s -* Dart Sass from a script snapshot: 0.179s +* sassc: 0.003s +* Dart Sass from a script snapshot: 0.191s * Dart Sass native executable: 0.009s -* Dart Sass on Node.js: 0.248s +* Dart Sass on Node.js: 0.224s Based on these numbers, Dart Sass from a native executable is approximately: -* 4.5x slower than libsass -* 27.6x faster than Dart Sass on Node +* 3.0x slower than libsass +* 24.9x faster than Dart Sass on Node ## Large Plain CSS Running on a file containing 2^17 instances of `.foo {a: b}`: -* sassc: 1.770s -* Dart Sass from a script snapshot: 1.548s -* Dart Sass native executable: 1.379s -* Dart Sass on Node.js: 2.587s +* sassc: 1.612s +* Dart Sass from a script snapshot: 1.663s +* Dart Sass native executable: 1.485s +* Dart Sass on Node.js: 2.583s Based on these numbers, Dart Sass from a native executable is approximately: -* 1.3x faster than libsass -* 1.9x faster than Dart Sass on Node +* 1.1x faster than libsass +* 1.7x faster than Dart Sass on Node ## 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.797s -* Dart Sass from a script snapshot: 1.594s -* Dart Sass native executable: 1.490s -* Dart Sass on Node.js: 2.783s +* sassc: 1.644s +* Dart Sass from a script snapshot: 1.721s +* Dart Sass native executable: 1.506s +* Dart Sass on Node.js: 2.613s Based on these numbers, Dart Sass from a native executable is approximately: -* 1.2x faster than libsass -* 1.9x faster than Dart Sass on Node +* 1.1x faster than libsass +* 1.7x faster than Dart Sass on Node ## 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.902s -* Dart Sass from a script snapshot: 1.587s -* Dart Sass native executable: 1.425s -* Dart Sass on Node.js: 2.550s +* sassc: 1.643s +* Dart Sass from a script snapshot: 1.655s +* Dart Sass native executable: 1.504s +* Dart Sass on Node.js: 2.625s Based on these numbers, Dart Sass from a native executable is approximately: -* 1.3x faster than libsass -* 1.8x faster than Dart Sass on Node +* 1.1x faster than libsass +* 1.7x faster than Dart Sass on Node ## Preceding Dense `@extend` Running on a file containing `.bar {@extend .foo}` followed by 2^17 instances of `.foo {a: b}`: -* sassc: 2.556s -* Dart Sass from a script snapshot: 2.426s -* Dart Sass native executable: 2.293s -* Dart Sass on Node.js: 4.843s +* sassc: 2.331s +* Dart Sass from a script snapshot: 2.433s +* Dart Sass native executable: 2.264s +* Dart Sass on Node.js: 5.822s Based on these numbers, Dart Sass from a native executable is approximately: -* 1.1x faster than libsass -* 2.1x faster than Dart Sass on Node +* identical to libsass +* 2.6x faster than Dart Sass on Node ## Following Dense `@extend` Running on a file containing 2^17 instances of `.foo {a: b}` followed by `.bar {@extend .foo}`: -* sassc: 2.567s -* Dart Sass from a script snapshot: 2.270s -* Dart Sass native executable: 2.174s -* Dart Sass on Node.js: 4.285s +* sassc: 2.367s +* Dart Sass from a script snapshot: 2.367s +* Dart Sass native executable: 2.189s +* Dart Sass on Node.js: 5.612s Based on these numbers, Dart Sass from a native executable is approximately: -* 1.2x faster than libsass -* 2.0x faster than Dart Sass on Node +* 1.1x faster than libsass +* 2.6x faster than Dart Sass on Node ## Bootstrap Running on a file containing 16 instances of importing the Bootstrap framework: -* sassc: 0.798s -* Dart Sass from a script snapshot: 1.417s -* Dart Sass native executable: 0.708s -* Dart Sass on Node.js: 2.832s +* sassc: 0.791s +* Dart Sass from a script snapshot: 1.707s +* Dart Sass native executable: 0.778s +* Dart Sass on Node.js: 3.101s Based on these numbers, Dart Sass from a native executable is approximately: -* 1.1x faster than libsass +* identical to libsass * 4.0x faster than Dart Sass on Node ## a11ycolor Running on a file containing test cases for a computation-intensive color-processing library: -* sassc: 0.239s -* Dart Sass from a script snapshot: 0.661s -* Dart Sass native executable: 0.319s -* Dart Sass on Node.js: 0.882s +* sassc: 0.207s +* Dart Sass from a script snapshot: 0.700s +* Dart Sass native executable: 0.267s +* Dart Sass on Node.js: 0.953s Based on these numbers, Dart Sass from a native executable is approximately: * 1.3x slower than libsass -* 2.8x faster than Dart Sass on Node - -## Susy - -Running on a file containing test cases for the computation-intensive Susy grid framework: - -* sassc: 0.201s -* Dart Sass from a script snapshot: 0.706s -* Dart Sass native executable: 0.141s -* Dart Sass on Node.js: 1.187s - -Based on these numbers, Dart Sass from a native executable is approximately: - -* 1.4x faster than libsass -* 8.4x faster than Dart Sass on Node +* 3.6x faster than Dart Sass on Node ## Duomo -Running on a file containing the output of the numerically-intensive Duomo framework: +Running on a file containing the output of the numerically-intensive Duomo framework (skipping LibSass due to module system use): -* Dart Sass from a script snapshot: 2.017s -* Dart Sass native executable: 1.213s -* Dart Sass on Node.js: 3.632s +* Dart Sass from a script snapshot: 2.298s +* Dart Sass native executable: 1.361s +* Dart Sass on Node.js: 4.659s Based on these numbers, Dart Sass from a native executable is approximately: -* 3.0x faster than Dart Sass on Node +* 3.4x faster than Dart Sass on Node + +## Carbon + +Running on a file containing the output of the import-intensive Carbon framework: + +* sassc: 6.576s +* Dart Sass from a script snapshot: 9.662s +* Dart Sass native executable: 9.874s +* Dart Sass on Node.js: 25.425s + +Based on these numbers, Dart Sass from a native executable is approximately: + +* 1.5x slower than libsass +* 2.6x faster than Dart Sass on Node # Prior Measurements From 6069708a83dccf56fd39be9282c6ee2c5d76f44e Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 15 Oct 2021 16:10:52 -0700 Subject: [PATCH 3/6] Cache hash codes for Sass strings and numbers These values are often stored in maps, so caching their hash codes seems to be net valuable. --- CHANGELOG.md | 4 ++++ lib/src/value/number.dart | 8 +++++++- lib/src/value/number/single_unit.dart | 3 ++- lib/src/value/number/unitless.dart | 2 +- lib/src/value/string.dart | 5 ++++- pkg/sass_api/CHANGELOG.md | 4 ++++ pkg/sass_api/pubspec.yaml | 4 ++-- pubspec.yaml | 2 +- 8 files changed, 25 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fe79b90..5de856de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.43.3 + +* Improve performance. + ## 1.43.2 * Improve the error message when the default namespace of a `@use` rule is not diff --git a/lib/src/value/number.dart b/lib/src/value/number.dart index cb875e0a..29b199e4 100644 --- a/lib/src/value/number.dart +++ b/lib/src/value/number.dart @@ -181,6 +181,12 @@ abstract class SassNumber extends Value { /// integer value, or [assertInt] to do both at once. final num value; + /// The cached hash code for this number, if it's been computed. + /// + /// @nodoc + @protected + int? hashCache; + /// This number's numerator units. List get numeratorUnits; @@ -826,7 +832,7 @@ abstract class SassNumber extends Value { } } - int get hashCode => fuzzyHashCode(value * + int get hashCode => hashCache ??= fuzzyHashCode(value * _canonicalMultiplier(numeratorUnits) / _canonicalMultiplier(denominatorUnits)); diff --git a/lib/src/value/number/single_unit.dart b/lib/src/value/number/single_unit.dart index 03b298a0..b52ad0e1 100644 --- a/lib/src/value/number/single_unit.dart +++ b/lib/src/value/number/single_unit.dart @@ -160,5 +160,6 @@ class SingleUnitSassNumber extends SassNumber { } } - int get hashCode => fuzzyHashCode(value * canonicalMultiplierForUnit(_unit)); + int get hashCode => + hashCache ??= fuzzyHashCode(value * canonicalMultiplierForUnit(_unit)); } diff --git a/lib/src/value/number/unitless.dart b/lib/src/value/number/unitless.dart index bc678b06..4964fde9 100644 --- a/lib/src/value/number/unitless.dart +++ b/lib/src/value/number/unitless.dart @@ -142,5 +142,5 @@ class UnitlessSassNumber extends SassNumber { bool operator ==(Object other) => other is UnitlessSassNumber && fuzzyEquals(value, other.value); - int get hashCode => fuzzyHashCode(value); + int get hashCode => hashCache ??= fuzzyHashCode(value); } diff --git a/lib/src/value/string.dart b/lib/src/value/string.dart index 10f5d2e4..4da67632 100644 --- a/lib/src/value/string.dart +++ b/lib/src/value/string.dart @@ -55,6 +55,9 @@ class SassString extends Value { /// efficient. late final int sassLength = text.runes.length; + /// The cached hash code for this number, if it's been computed. + int? _hashCache; + /// @nodoc @internal bool get isSpecialNumber { @@ -189,7 +192,7 @@ class SassString extends Value { bool operator ==(Object other) => other is SassString && text == other.text; - int get hashCode => text.hashCode; + int get hashCode => _hashCache ??= text.hashCode; /// Throws a [SassScriptException] with the given [message]. SassScriptException _exception(String message, [String? name]) => diff --git a/pkg/sass_api/CHANGELOG.md b/pkg/sass_api/CHANGELOG.md index ff968fe3..0536bda8 100644 --- a/pkg/sass_api/CHANGELOG.md +++ b/pkg/sass_api/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.0-beta.17 + +* No user-visible changes. + ## 1.0.0-beta.16 * No user-visible changes. diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index e1c72fe4..bec75b19 100644 --- a/pkg/sass_api/pubspec.yaml +++ b/pkg/sass_api/pubspec.yaml @@ -2,7 +2,7 @@ name: sass_api # Note: Every time we add a new Sass AST node, we need to bump the *major* # version because it's a breaking change for anyone who's implementing the # visitor interface(s). -version: 1.0.0-beta.16 +version: 1.0.0-beta.17 description: Additional APIs for Dart Sass. homepage: https://github.com/sass/dart-sass @@ -10,7 +10,7 @@ environment: sdk: '>=2.12.0 <3.0.0' dependencies: - sass: 1.43.2 + sass: 1.43.3 dependency_overrides: sass: {path: ../..} diff --git a/pubspec.yaml b/pubspec.yaml index c1efeea1..5884225d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.43.2 +version: 1.43.3 description: A Sass implementation in Dart. homepage: https://github.com/sass/dart-sass From d13a38ed19a24045912c097cb1d91699fedac562 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 15 Oct 2021 16:47:45 -0700 Subject: [PATCH 4/6] Cache relative import results as well as absolute ones This dramatically improves the performance of a sample app using the Carbon design system, and should generally improve apps that have a lot of repeated @imports of library files. It's possible we can back this out if it's not pulling its weight once we no longer support @import. --- lib/src/async_import_cache.dart | 61 ++++++++++++++++++++++----------- lib/src/import_cache.dart | 59 +++++++++++++++++++++---------- 2 files changed, 81 insertions(+), 39 deletions(-) diff --git a/lib/src/async_import_cache.dart b/lib/src/async_import_cache.dart index 4690329d..3817a450 100644 --- a/lib/src/async_import_cache.dart +++ b/lib/src/async_import_cache.dart @@ -33,16 +33,31 @@ class AsyncImportCache { /// /// This map's values are the same as the return value of [canonicalize]. /// - /// This cache isn't used for relative imports, because they're - /// context-dependent. - final Map, Tuple3?> - _canonicalizeCache; + /// This cache isn't used for relative imports, because they depend on the + /// specific base importer. That's stored separately in + /// [_relativeCanonicalizeCache]. + final _canonicalizeCache = + , Tuple3?>{}; + + /// The canonicalized URLs for each non-canonical URL that's resolved using a + /// relative importer. + /// + /// The map's keys have four parts: + /// + /// 1. The URL passed to [canonicalize] (the same as in [_canonicalizeCache]). + /// 2. Whether the canonicalization is for an `@import` rule. + /// 3. The `baseImporter` passed to [canonicalize]. + /// 4. The `baseUrl` passed to [canonicalize]. + /// + /// The map's values are the same as the return value of [canonicalize]. + final _relativeCanonicalizeCache = , + Tuple3?>{}; /// The parsed stylesheets for each canonicalized import URL. - final Map _importCache; + final _importCache = {}; /// The import results for each canonicalized import URL. - final Map _resultsCache; + final _resultsCache = {}; /// Creates an import cache that resolves imports using [importers]. /// @@ -67,18 +82,12 @@ class AsyncImportCache { PackageConfig? packageConfig, Logger? logger}) : _importers = _toImporters(importers, loadPaths, packageConfig), - _logger = logger ?? const Logger.stderr(), - _canonicalizeCache = {}, - _importCache = {}, - _resultsCache = {}; + _logger = logger ?? const Logger.stderr(); /// Creates an import cache without any globally-available importers. AsyncImportCache.none({Logger? logger}) : _importers = const [], - _logger = logger ?? const Logger.stderr(), - _canonicalizeCache = {}, - _importCache = {}, - _resultsCache = {}; + _logger = logger ?? const Logger.stderr(); /// Converts the user's [importers], [loadPaths], and [packageConfig] /// options into a single list of importers. @@ -113,12 +122,16 @@ class AsyncImportCache { Uri? baseUrl, bool forImport = false}) async { if (baseImporter != null) { - var resolvedUrl = baseUrl?.resolveUri(url) ?? url; - var canonicalUrl = - await _canonicalize(baseImporter, resolvedUrl, forImport); - if (canonicalUrl != null) { - return Tuple3(baseImporter, canonicalUrl, resolvedUrl); - } + var relativeResult = await putIfAbsentAsync(_relativeCanonicalizeCache, + Tuple4(url, forImport, baseImporter, baseUrl), () async { + var resolvedUrl = baseUrl?.resolveUri(url) ?? url; + var canonicalUrl = + await _canonicalize(baseImporter, resolvedUrl, forImport); + if (canonicalUrl != null) { + return Tuple3(baseImporter, canonicalUrl, resolvedUrl); + } + }); + if (relativeResult != null) return relativeResult; } return await putIfAbsentAsync(_canonicalizeCache, Tuple2(url, forImport), @@ -236,6 +249,14 @@ Relative canonical URLs are deprecated and will eventually be disallowed. void clearCanonicalize(Uri url) { _canonicalizeCache.remove(Tuple2(url, false)); _canonicalizeCache.remove(Tuple2(url, true)); + + var relativeKeysToClear = [ + for (var key in _relativeCanonicalizeCache.keys) + if (key.item1 == url) key + ]; + for (var key in relativeKeysToClear) { + _relativeCanonicalizeCache.remove(key); + } } /// Clears the cached parse tree for the stylesheet with the given diff --git a/lib/src/import_cache.dart b/lib/src/import_cache.dart index 60e40024..5d46bc23 100644 --- a/lib/src/import_cache.dart +++ b/lib/src/import_cache.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_import_cache.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: b3b80fe96623c1579809528e46d9c75b87bf82ea +// Checksum: 3e290e40f4576be99217ddfbd7a76c4d38721af1 // // ignore_for_file: unused_import @@ -40,15 +40,30 @@ class ImportCache { /// /// This map's values are the same as the return value of [canonicalize]. /// - /// This cache isn't used for relative imports, because they're - /// context-dependent. - final Map, Tuple3?> _canonicalizeCache; + /// This cache isn't used for relative imports, because they depend on the + /// specific base importer. That's stored separately in + /// [_relativeCanonicalizeCache]. + final _canonicalizeCache = , Tuple3?>{}; + + /// The canonicalized URLs for each non-canonical URL that's resolved using a + /// relative importer. + /// + /// The map's keys have four parts: + /// + /// 1. The URL passed to [canonicalize] (the same as in [_canonicalizeCache]). + /// 2. Whether the canonicalization is for an `@import` rule. + /// 3. The `baseImporter` passed to [canonicalize]. + /// 4. The `baseUrl` passed to [canonicalize]. + /// + /// The map's values are the same as the return value of [canonicalize]. + final _relativeCanonicalizeCache = + , Tuple3?>{}; /// The parsed stylesheets for each canonicalized import URL. - final Map _importCache; + final _importCache = {}; /// The import results for each canonicalized import URL. - final Map _resultsCache; + final _resultsCache = {}; /// Creates an import cache that resolves imports using [importers]. /// @@ -73,18 +88,12 @@ class ImportCache { PackageConfig? packageConfig, Logger? logger}) : _importers = _toImporters(importers, loadPaths, packageConfig), - _logger = logger ?? const Logger.stderr(), - _canonicalizeCache = {}, - _importCache = {}, - _resultsCache = {}; + _logger = logger ?? const Logger.stderr(); /// Creates an import cache without any globally-available importers. ImportCache.none({Logger? logger}) : _importers = const [], - _logger = logger ?? const Logger.stderr(), - _canonicalizeCache = {}, - _importCache = {}, - _resultsCache = {}; + _logger = logger ?? const Logger.stderr(); /// Converts the user's [importers], [loadPaths], and [packageConfig] /// options into a single list of importers. @@ -117,11 +126,15 @@ class ImportCache { Tuple3? canonicalize(Uri url, {Importer? baseImporter, Uri? baseUrl, bool forImport = false}) { if (baseImporter != null) { - var resolvedUrl = baseUrl?.resolveUri(url) ?? url; - var canonicalUrl = _canonicalize(baseImporter, resolvedUrl, forImport); - if (canonicalUrl != null) { - return Tuple3(baseImporter, canonicalUrl, resolvedUrl); - } + var relativeResult = _relativeCanonicalizeCache + .putIfAbsent(Tuple4(url, forImport, baseImporter, baseUrl), () { + var resolvedUrl = baseUrl?.resolveUri(url) ?? url; + var canonicalUrl = _canonicalize(baseImporter, resolvedUrl, forImport); + if (canonicalUrl != null) { + return Tuple3(baseImporter, canonicalUrl, resolvedUrl); + } + }); + if (relativeResult != null) return relativeResult; } return _canonicalizeCache.putIfAbsent(Tuple2(url, forImport), () { @@ -235,6 +248,14 @@ Relative canonical URLs are deprecated and will eventually be disallowed. void clearCanonicalize(Uri url) { _canonicalizeCache.remove(Tuple2(url, false)); _canonicalizeCache.remove(Tuple2(url, true)); + + var relativeKeysToClear = [ + for (var key in _relativeCanonicalizeCache.keys) + if (key.item1 == url) key + ]; + for (var key in relativeKeysToClear) { + _relativeCanonicalizeCache.remove(key); + } } /// Clears the cached parse tree for the stylesheet with the given From 2430ac6d201d15a39f5eb4c50e3c0e8359b28d05 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 15 Oct 2021 17:21:24 -0700 Subject: [PATCH 5/6] Avoid creating zone variables inside _EvaluateVisitor Instead, we create a single zone-scoped object that's visible for the entire lifespan of the visitor, and which exposes evaluation internals which can be updated as direct field modifications. --- lib/sass.dart | 2 +- lib/src/evaluation_context.dart | 57 +++++++++++++++++++++++++++++ lib/src/functions.dart | 21 ----------- lib/src/functions/color.dart | 2 +- lib/src/functions/math.dart | 2 +- lib/src/functions/selector.dart | 8 ++-- lib/src/visitor/async_evaluate.dart | 55 ++++++++++++++++++---------- lib/src/visitor/evaluate.dart | 57 ++++++++++++++++++----------- lib/src/warn.dart | 36 ------------------ test/dart_api/logger_test.dart | 2 +- 10 files changed, 137 insertions(+), 105 deletions(-) create mode 100644 lib/src/evaluation_context.dart delete mode 100644 lib/src/warn.dart diff --git a/lib/sass.dart b/lib/sass.dart index d303bcbf..3ac5d66e 100644 --- a/lib/sass.dart +++ b/lib/sass.dart @@ -28,7 +28,7 @@ export 'src/logger.dart'; export 'src/syntax.dart'; export 'src/value.dart' hide SassApiColor; export 'src/visitor/serialize.dart' show OutputStyle; -export 'src/warn.dart' show warn; +export 'src/evaluation_context.dart' show warn; /// Loads the Sass file at [path], compiles it to CSS, and returns a /// [CompileResult] containing the CSS and additional metadata about the diff --git a/lib/src/evaluation_context.dart b/lib/src/evaluation_context.dart new file mode 100644 index 00000000..5a347f7f --- /dev/null +++ b/lib/src/evaluation_context.dart @@ -0,0 +1,57 @@ +// Copyright 2021 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:async'; + +import 'package:source_span/source_span.dart'; + +/// An interface that exposes information about the current Sass evaluation. +/// +/// This allows us to expose zone-scoped information without having to create a +/// new zone variable for each piece of information. +abstract class EvaluationContext { + /// The current evaluation context. + /// + /// Throws [StateError] if there isn't a Sass stylesheet currently being + /// evaluated. + static EvaluationContext get current { + var context = Zone.current[#_evaluationContext]; + if (context is EvaluationContext) return context; + throw StateError("No Sass stylesheet is currently being evaluated."); + } + + /// Returns the span for the currently executing callable. + /// + /// For normal exception reporting, this should be avoided in favor of + /// throwing [SassScriptException]s. It should only be used when calling APIs + /// that require spans. + /// + /// Throws a [StateError] if there isn't a callable being invoked. + FileSpan get currentCallableSpan; + + /// Prints a warning message associated with the current `@import` or function + /// call. + /// + /// If [deprecation] is `true`, the warning is emitted as a deprecation + /// warning. + void warn(String message, {bool deprecation = false}); +} + +/// Prints a warning message associated with the current `@import` or function +/// call. +/// +/// If [deprecation] is `true`, the warning is emitted as a deprecation warning. +/// +/// This may only be called within a custom function or importer callback. +/// +/// {@category Compile} +void warn(String message, {bool deprecation = false}) => + EvaluationContext.current.warn(message, deprecation: deprecation); + +/// Runs [callback] with [context] as [EvaluationContext.current]. +/// +/// This is zone-based, so if [callback] is asynchronous [warn] is set for the +/// duration of that callback. +T withEvaluationContext(EvaluationContext context, T callback()) => + runZoned(callback, zoneValues: {#_evaluationContext: context}); diff --git a/lib/src/functions.dart b/lib/src/functions.dart index f83695b1..d29de59a 100644 --- a/lib/src/functions.dart +++ b/lib/src/functions.dart @@ -3,12 +3,9 @@ // https://opensource.org/licenses/MIT. import 'dart:collection'; -import 'dart:async'; import 'package:collection/collection.dart'; -import 'package:source_span/source_span.dart'; -import 'ast/node.dart'; import 'callable.dart'; import 'functions/color.dart' as color; import 'functions/list.dart' as list; @@ -49,21 +46,3 @@ final coreModules = UnmodifiableListView([ selector.module, string.module ]); - -/// Returns the span for the currently executing callable. -/// -/// For normal exception reporting, this should be avoided in favor of throwing -/// [SassScriptException]s. It should only be used when calling APIs that -/// require spans. -FileSpan get currentCallableSpan { - var node = Zone.current[#_currentCallableNode]; - if (node is AstNode) return node.span; - - throw StateError("currentCallableSpan may only be called within an " - "active Sass callable."); -} - -/// Runs [callback] in a zone with [callableNode]'s span available from -/// [currentCallableSpan]. -T withCurrentCallableNode(AstNode callableNode, T callback()) => - runZoned(callback, zoneValues: {#_currentCallableNode: callableNode}); diff --git a/lib/src/functions/color.dart b/lib/src/functions/color.dart index 9b70540c..9dc7348a 100644 --- a/lib/src/functions/color.dart +++ b/lib/src/functions/color.dart @@ -7,13 +7,13 @@ import 'dart:collection'; import 'package:collection/collection.dart'; import '../callable.dart'; +import '../evaluation_context.dart'; import '../exception.dart'; import '../module/built_in.dart'; import '../util/number.dart'; import '../util/nullable.dart'; import '../utils.dart'; import '../value.dart'; -import '../warn.dart'; /// A regular expression matching the beginning of a proprietary Microsoft /// filter declaration. diff --git a/lib/src/functions/math.dart b/lib/src/functions/math.dart index ff60d763..f9c40610 100644 --- a/lib/src/functions/math.dart +++ b/lib/src/functions/math.dart @@ -8,11 +8,11 @@ import 'dart:math' as math; import 'package:collection/collection.dart'; import '../callable.dart'; +import '../evaluation_context.dart'; import '../exception.dart'; import '../module/built_in.dart'; import '../util/number.dart'; import '../value.dart'; -import '../warn.dart'; /// The global definitions of Sass math functions. final global = UnmodifiableListView([ diff --git a/lib/src/functions/selector.dart b/lib/src/functions/selector.dart index 83eb77e8..442330e3 100644 --- a/lib/src/functions/selector.dart +++ b/lib/src/functions/selector.dart @@ -8,9 +8,9 @@ import 'package:collection/collection.dart'; import '../ast/selector.dart'; import '../callable.dart'; +import '../evaluation_context.dart'; import '../exception.dart'; import '../extend/extension_store.dart'; -import '../functions.dart'; import '../module/built_in.dart'; import '../value.dart'; @@ -88,7 +88,8 @@ final _extend = var target = arguments[1].assertSelector(name: "extendee"); var source = arguments[2].assertSelector(name: "extender"); - return ExtensionStore.extend(selector, source, target, currentCallableSpan) + return ExtensionStore.extend(selector, source, target, + EvaluationContext.current.currentCallableSpan) .asSassList; }); @@ -98,7 +99,8 @@ final _replace = var target = arguments[1].assertSelector(name: "original"); var source = arguments[2].assertSelector(name: "replacement"); - return ExtensionStore.replace(selector, source, target, currentCallableSpan) + return ExtensionStore.replace(selector, source, target, + EvaluationContext.current.currentCallableSpan) .asSassList; }); diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 56a8cb90..573b240c 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -23,6 +23,7 @@ import '../callable.dart'; import '../color_names.dart'; import '../configuration.dart'; import '../configured_value.dart'; +import '../evaluation_context.dart'; import '../exception.dart'; import '../extend/extension_store.dart'; import '../extend/extension.dart'; @@ -39,7 +40,6 @@ import '../syntax.dart'; import '../utils.dart'; import '../util/nullable.dart'; import '../value.dart'; -import '../warn.dart'; import 'interface/css.dart'; import 'interface/expression.dart'; import 'interface/modifiable_css.dart'; @@ -498,7 +498,7 @@ class _EvaluateVisitor } Future run(AsyncImporter? importer, Stylesheet node) async { - return _withWarnCallback(node, () async { + return withEvaluationContext(_EvaluationContext(this, node), () async { var url = node.span.sourceUrl; if (url != null) { _activeModules[url] = null; @@ -512,29 +512,17 @@ class _EvaluateVisitor } Future runExpression(AsyncImporter? importer, Expression expression) => - _withWarnCallback( - expression, + withEvaluationContext( + _EvaluationContext(this, expression), () => _withFakeStylesheet( importer, expression, () => expression.accept(this))); Future runStatement(AsyncImporter? importer, Statement statement) => - _withWarnCallback( - statement, + withEvaluationContext( + _EvaluationContext(this, statement), () => _withFakeStylesheet( importer, statement, () => statement.accept(this))); - /// Runs [callback] with a definition for the top-level `warn` function. - /// - /// If no other span can be found to report a warning, falls back on - /// [nodeWithSpan]'s. - T _withWarnCallback(AstNode nodeWithSpan, T callback()) { - return withWarnCallback( - (message, deprecation) => _warn( - message, _importSpan ?? _callableNode?.span ?? nodeWithSpan.span, - deprecation: deprecation), - callback); - } - /// Asserts that [value] is not `null` and returns it. /// /// This is used for fields that are set whenever the evaluator is evaluating @@ -2590,8 +2578,7 @@ class _EvaluateVisitor Value result; try { - result = await withCurrentCallableNode( - nodeWithSpan, () => callback(evaluated.positional)); + result = await callback(evaluated.positional); } on SassRuntimeException { rethrow; } on MultiSpanSassScriptException catch (error) { @@ -3462,6 +3449,34 @@ class EvaluateResult { EvaluateResult(this.stylesheet, this.loadedUrls); } +/// An implementation of [EvaluationContext] using the information available in +/// [_EvaluateVisitor]. +class _EvaluationContext implements EvaluationContext { + /// The visitor backing this context. + final _EvaluateVisitor _visitor; + + /// The AST node whose span should be used for [warn] if no other span is + /// avaiable. + final AstNode _defaultWarnNodeWithSpan; + + _EvaluationContext(this._visitor, this._defaultWarnNodeWithSpan); + + FileSpan get currentCallableSpan { + var callableNode = _visitor._callableNode; + if (callableNode != null) return callableNode.span; + throw StateError("No Sass callable is currently being evaluated."); + } + + void warn(String message, {bool deprecation = false}) { + _visitor._warn( + message, + _visitor._importSpan ?? + _visitor._callableNode?.span ?? + _defaultWarnNodeWithSpan.span, + deprecation: deprecation); + } +} + /// The result of evaluating arguments to a function or mixin. class _ArgumentResults { /// Arguments passed by position. diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index 38d97a5d..752c8db6 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 7a3ae06379ca09dbf3e92d01c1fd974c5b3b9154 +// Checksum: 5cdb3467b517bf381d525a1a4bc4f9b6a0eeefad // // ignore_for_file: unused_import @@ -32,6 +32,7 @@ import '../callable.dart'; import '../color_names.dart'; import '../configuration.dart'; import '../configured_value.dart'; +import '../evaluation_context.dart'; import '../exception.dart'; import '../extend/extension_store.dart'; import '../extend/extension.dart'; @@ -48,7 +49,6 @@ import '../syntax.dart'; import '../utils.dart'; import '../util/nullable.dart'; import '../value.dart'; -import '../warn.dart'; import 'interface/css.dart'; import 'interface/expression.dart'; import 'interface/modifiable_css.dart'; @@ -503,7 +503,7 @@ class _EvaluateVisitor } EvaluateResult run(Importer? importer, Stylesheet node) { - return _withWarnCallback(node, () { + return withEvaluationContext(_EvaluationContext(this, node), () { var url = node.span.sourceUrl; if (url != null) { _activeModules[url] = null; @@ -517,29 +517,17 @@ class _EvaluateVisitor } Value runExpression(Importer? importer, Expression expression) => - _withWarnCallback( - expression, + withEvaluationContext( + _EvaluationContext(this, expression), () => _withFakeStylesheet( importer, expression, () => expression.accept(this))); void runStatement(Importer? importer, Statement statement) => - _withWarnCallback( - statement, + withEvaluationContext( + _EvaluationContext(this, statement), () => _withFakeStylesheet( importer, statement, () => statement.accept(this))); - /// Runs [callback] with a definition for the top-level `warn` function. - /// - /// If no other span can be found to report a warning, falls back on - /// [nodeWithSpan]'s. - T _withWarnCallback(AstNode nodeWithSpan, T callback()) { - return withWarnCallback( - (message, deprecation) => _warn( - message, _importSpan ?? _callableNode?.span ?? nodeWithSpan.span, - deprecation: deprecation), - callback); - } - /// Asserts that [value] is not `null` and returns it. /// /// This is used for fields that are set whenever the evaluator is evaluating @@ -2573,8 +2561,7 @@ class _EvaluateVisitor Value result; try { - result = withCurrentCallableNode( - nodeWithSpan, () => callback(evaluated.positional)); + result = callback(evaluated.positional); } on SassRuntimeException { rethrow; } on MultiSpanSassScriptException catch (error) { @@ -3403,6 +3390,34 @@ class _ImportedCssVisitor implements ModifiableCssVisitor { _visitor._addChild(node, through: (node) => node is CssStyleRule); } +/// An implementation of [EvaluationContext] using the information available in +/// [_EvaluateVisitor]. +class _EvaluationContext implements EvaluationContext { + /// The visitor backing this context. + final _EvaluateVisitor _visitor; + + /// The AST node whose span should be used for [warn] if no other span is + /// avaiable. + final AstNode _defaultWarnNodeWithSpan; + + _EvaluationContext(this._visitor, this._defaultWarnNodeWithSpan); + + FileSpan get currentCallableSpan { + var callableNode = _visitor._callableNode; + if (callableNode != null) return callableNode.span; + throw StateError("No Sass callable is currently being evaluated."); + } + + void warn(String message, {bool deprecation = false}) { + _visitor._warn( + message, + _visitor._importSpan ?? + _visitor._callableNode?.span ?? + _defaultWarnNodeWithSpan.span, + deprecation: deprecation); + } +} + /// The result of evaluating arguments to a function or mixin. class _ArgumentResults { /// Arguments passed by position. diff --git a/lib/src/warn.dart b/lib/src/warn.dart deleted file mode 100644 index 981d0bc6..00000000 --- a/lib/src/warn.dart +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2019 Google Inc. Use of this source code is governed by an -// MIT-style license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. - -import 'dart:async'; - -/// Prints a warning message associated with the current `@import` or function -/// call. -/// -/// If [deprecation] is `true`, the warning is emitted as a deprecation warning. -/// -/// This may only be called within a custom function or importer callback. -/// -/// {@category Compile} -void warn(String message, {bool deprecation = false}) { - var warnDefinition = Zone.current[#_warn]; - - if (warnDefinition == null) { - throw ArgumentError( - "warn() may only be called within a custom function or importer " - "callback."); - } - - warnDefinition(message, deprecation); -} - -/// Runs [callback] with [warn] as the definition for the top-level `warn()` function. -/// -/// This is zone-based, so if [callback] is asynchronous [warn] is set for the -/// duration of that callback. -T withWarnCallback( - void warn(String message, bool deprecation), T callback()) { - return runZoned(() { - return callback(); - }, zoneValues: {#_warn: warn}); -} diff --git a/test/dart_api/logger_test.dart b/test/dart_api/logger_test.dart index b22564ff..a1c0ec93 100644 --- a/test/dart_api/logger_test.dart +++ b/test/dart_api/logger_test.dart @@ -229,7 +229,7 @@ void main() { }); test("throws an error outside a callback", () { - expect(() => warn("heck"), throwsArgumentError); + expect(() => warn("heck"), throwsStateError); }); }); } From 86b16f5500c9a53d248979890a0bc71cfc812315 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 15 Oct 2021 18:41:01 -0700 Subject: [PATCH 6/6] Update benchmarks --- perf.md | 100 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/perf.md b/perf.md index 469083a1..5d8ad91b 100644 --- a/perf.md +++ b/perf.md @@ -4,7 +4,7 @@ the benefit Dart Sass could provide relative to other implementations. This was tested against: * libsass da91d985 and sassc 66f0ef3 compiled with g++ (Debian 10.3.0-11) 10.3.0. -* Dart Sass 7934ad9 on Dart 2.14.1 (stable) (Wed Sep 8 13:33:08 2021 +0200) on "linux_x64" and Node v16.10.0. +* Dart Sass bf318a8 on Dart 2.14.1 (stable) (Wed Sep 8 13:33:08 2021 +0200) on "linux_x64" and Node v16.10.0. on Debian x64 with Intel Core i7-8650U CPU @ 1.90GHz. @@ -16,24 +16,24 @@ 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.003s -* Dart Sass from a script snapshot: 0.191s +* sassc: 0.002s +* Dart Sass from a script snapshot: 0.177s * Dart Sass native executable: 0.009s -* Dart Sass on Node.js: 0.224s +* Dart Sass on Node.js: 0.219s Based on these numbers, Dart Sass from a native executable is approximately: -* 3.0x slower than libsass -* 24.9x faster than Dart Sass on Node +* 4.5x slower than libsass +* 24.3x faster than Dart Sass on Node ## Large Plain CSS Running on a file containing 2^17 instances of `.foo {a: b}`: -* sassc: 1.612s -* Dart Sass from a script snapshot: 1.663s -* Dart Sass native executable: 1.485s -* Dart Sass on Node.js: 2.583s +* sassc: 1.607s +* Dart Sass from a script snapshot: 1.643s +* Dart Sass native executable: 1.473s +* Dart Sass on Node.js: 2.529s Based on these numbers, Dart Sass from a native executable is approximately: @@ -44,10 +44,10 @@ Based on these numbers, Dart Sass from a native executable is approximately: Running on a file containing `.x {@extend .y}`, 2^17 instances of `.foo {a: b}`, and then `.y {a: b}`: -* sassc: 1.644s -* Dart Sass from a script snapshot: 1.721s -* Dart Sass native executable: 1.506s -* Dart Sass on Node.js: 2.613s +* sassc: 1.643s +* Dart Sass from a script snapshot: 1.723s +* Dart Sass native executable: 1.535s +* Dart Sass on Node.js: 2.574s Based on these numbers, Dart Sass from a native executable is approximately: @@ -58,10 +58,10 @@ Based on these numbers, Dart Sass from a native executable is approximately: Running on a file containing `.y {a: b}`, 2^17 instances of `.foo {a: b}`, and then `.x {@extend .y}`: -* sassc: 1.643s -* Dart Sass from a script snapshot: 1.655s -* Dart Sass native executable: 1.504s -* Dart Sass on Node.js: 2.625s +* sassc: 1.642s +* Dart Sass from a script snapshot: 1.676s +* Dart Sass native executable: 1.517s +* Dart Sass on Node.js: 2.547s Based on these numbers, Dart Sass from a native executable is approximately: @@ -72,83 +72,83 @@ Based on these numbers, Dart Sass from a native executable is approximately: Running on a file containing `.bar {@extend .foo}` followed by 2^17 instances of `.foo {a: b}`: -* sassc: 2.331s -* Dart Sass from a script snapshot: 2.433s -* Dart Sass native executable: 2.264s -* Dart Sass on Node.js: 5.822s +* sassc: 2.336s +* Dart Sass from a script snapshot: 2.453s +* Dart Sass native executable: 2.312s +* Dart Sass on Node.js: 5.874s Based on these numbers, Dart Sass from a native executable is approximately: * identical to libsass -* 2.6x faster than Dart Sass on Node +* 2.5x faster than Dart Sass on Node ## Following Dense `@extend` Running on a file containing 2^17 instances of `.foo {a: b}` followed by `.bar {@extend .foo}`: -* sassc: 2.367s -* Dart Sass from a script snapshot: 2.367s -* Dart Sass native executable: 2.189s -* Dart Sass on Node.js: 5.612s +* sassc: 2.353s +* Dart Sass from a script snapshot: 2.357s +* Dart Sass native executable: 2.220s +* Dart Sass on Node.js: 5.587s Based on these numbers, Dart Sass from a native executable is approximately: * 1.1x faster than libsass -* 2.6x faster than Dart Sass on Node +* 2.5x faster than Dart Sass on Node ## Bootstrap Running on a file containing 16 instances of importing the Bootstrap framework: -* sassc: 0.791s -* Dart Sass from a script snapshot: 1.707s -* Dart Sass native executable: 0.778s -* Dart Sass on Node.js: 3.101s +* sassc: 0.789s +* Dart Sass from a script snapshot: 1.517s +* Dart Sass native executable: 0.691s +* Dart Sass on Node.js: 2.799s Based on these numbers, Dart Sass from a native executable is approximately: -* identical to libsass -* 4.0x faster than Dart Sass on Node +* 1.1x faster than libsass +* 4.1x faster than Dart Sass on Node ## a11ycolor Running on a file containing test cases for a computation-intensive color-processing library: -* sassc: 0.207s -* Dart Sass from a script snapshot: 0.700s -* Dart Sass native executable: 0.267s -* Dart Sass on Node.js: 0.953s +* sassc: 0.205s +* Dart Sass from a script snapshot: 0.649s +* Dart Sass native executable: 0.245s +* Dart Sass on Node.js: 0.827s 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.4x faster than Dart Sass on Node ## Duomo Running on a file containing the output of the numerically-intensive Duomo framework (skipping LibSass due to module system use): -* Dart Sass from a script snapshot: 2.298s -* Dart Sass native executable: 1.361s -* Dart Sass on Node.js: 4.659s +* Dart Sass from a script snapshot: 2.150s +* Dart Sass native executable: 1.406s +* Dart Sass on Node.js: 4.449s Based on these numbers, Dart Sass from a native executable is approximately: -* 3.4x faster than Dart Sass on Node +* 3.2x faster than Dart Sass on Node ## Carbon Running on a file containing the output of the import-intensive Carbon framework: -* sassc: 6.576s -* Dart Sass from a script snapshot: 9.662s -* Dart Sass native executable: 9.874s -* Dart Sass on Node.js: 25.425s +* sassc: 7.481s +* Dart Sass from a script snapshot: 5.891s +* Dart Sass native executable: 5.734s +* Dart Sass on Node.js: 15.725s Based on these numbers, Dart Sass from a native executable is approximately: -* 1.5x slower than libsass -* 2.6x faster than Dart Sass on Node +* 1.3x faster than libsass +* 2.7x faster than Dart Sass on Node # Prior Measurements