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.
This commit is contained in:
Natalie Weizenbaum 2021-10-15 16:47:45 -07:00
parent 6069708a83
commit d13a38ed19
2 changed files with 81 additions and 39 deletions

View File

@ -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<Tuple2<Uri, bool>, Tuple3<AsyncImporter, Uri, Uri>?>
_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 =
<Tuple2<Uri, bool>, Tuple3<AsyncImporter, Uri, Uri>?>{};
/// 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 = <Tuple4<Uri, bool, AsyncImporter, Uri?>,
Tuple3<AsyncImporter, Uri, Uri>?>{};
/// The parsed stylesheets for each canonicalized import URL.
final Map<Uri, Stylesheet?> _importCache;
final _importCache = <Uri, Stylesheet?>{};
/// The import results for each canonicalized import URL.
final Map<Uri, ImporterResult> _resultsCache;
final _resultsCache = <Uri, ImporterResult>{};
/// 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

View File

@ -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<Tuple2<Uri, bool>, Tuple3<Importer, Uri, Uri>?> _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 = <Tuple2<Uri, bool>, Tuple3<Importer, Uri, Uri>?>{};
/// 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 =
<Tuple4<Uri, bool, Importer, Uri?>, Tuple3<Importer, Uri, Uri>?>{};
/// The parsed stylesheets for each canonicalized import URL.
final Map<Uri, Stylesheet?> _importCache;
final _importCache = <Uri, Stylesheet?>{};
/// The import results for each canonicalized import URL.
final Map<Uri, ImporterResult> _resultsCache;
final _resultsCache = <Uri, ImporterResult>{};
/// 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<Importer, Uri, Uri>? 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