Skip to content

Commit d23db1b

Browse files
authored
Performance improvements (#2259)
When documenting the camera Flutter plugin, these changes reduce the time to document from 52 seconds to 51 seconds. Much of this speedup likely came from memoizing Package.documentedWhere, which has a few expensive calls like `linkToRemote` and `linkToUrl`. Additionally ModelElement.locationPieces is now memoized. The rest is a few refactorings: * ModelElement.canonicalLibrary: * Refactor most of the complexity of ModelElement.canonicalLibrary into a new private method. * Don't compute topLevelElement until you need it. * Short circuit if `definingLibrary.exportedInLibraries` is null. * Reduce nesting * Create warning message only if it will be used. * Prefer for loops over Iterable.forEach.
1 parent 036d76e commit d23db1b

File tree

6 files changed

+125
-117
lines changed

6 files changed

+125
-117
lines changed

lib/dartdoc.dart

+4-1
Original file line numberDiff line numberDiff line change
@@ -290,10 +290,10 @@ class Dartdoc {
290290
var indexJson = path.joinAll([normalOrigin, 'index.json']);
291291
var foundIndexJson = false;
292292
for (var f in Directory(normalOrigin).listSync(recursive: true)) {
293-
var fullPath = path.normalize(f.path);
294293
if (f is Directory) {
295294
continue;
296295
}
296+
var fullPath = path.normalize(f.path);
297297
if (fullPath.startsWith(staticAssets)) {
298298
continue;
299299
}
@@ -331,6 +331,9 @@ class Dartdoc {
331331
if (!file.existsSync()) {
332332
return null;
333333
}
334+
// TODO(srawlins): It is possible that instantiating an HtmlParser using
335+
// `lowercaseElementName: false` and `lowercaseAttrName: false` may save
336+
// time or memory.
334337
var doc = parse(file.readAsBytesSync());
335338
var base = doc.querySelector('base');
336339
String baseHref;

lib/src/model/canonicalization.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ abstract class Canonicalization implements Locatable, Documentable {
1515
/// Pieces of the location, split to remove 'package:' and slashes.
1616
Set<String> get locationPieces;
1717

18-
List<ScoredCandidate> scoreCanonicalCandidates(List<Library> libraries) {
19-
return libraries.map((l) => scoreElementWithLibrary(l)).toList()..sort();
18+
List<ScoredCandidate> scoreCanonicalCandidates(Iterable<Library> libraries) {
19+
return libraries.map(scoreElementWithLibrary).toList()..sort();
2020
}
2121

2222
ScoredCandidate scoreElementWithLibrary(Library lib) {

lib/src/model/library.dart

+3-7
Original file line numberDiff line numberDiff line change
@@ -628,13 +628,9 @@ class Library extends ModelElement with Categorization, TopLevelContainer {
628628
List<ModelElement> _allModelElements;
629629

630630
Iterable<ModelElement> get allModelElements {
631-
if (_allModelElements == null) {
632-
_allModelElements = [];
633-
for (var modelElements in modelElementsMap.values) {
634-
_allModelElements.addAll(modelElements);
635-
}
636-
}
637-
return _allModelElements;
631+
return _allModelElements ??= [
632+
for (var modelElements in modelElementsMap.values) ...modelElements,
633+
];
638634
}
639635

640636
List<ModelElement> _allCanonicalModelElements;

lib/src/model/model_element.dart

+85-76
Original file line numberDiff line numberDiff line change
@@ -473,13 +473,14 @@ abstract class ModelElement extends Canonicalization
473473
return _config;
474474
}
475475

476+
Set<String> _locationPieces;
477+
476478
@override
477-
Set<String> get locationPieces {
478-
return Set.from(element.location
479-
.toString()
480-
.split(locationSplitter)
481-
.where((s) => s.isNotEmpty));
482-
}
479+
Set<String> get locationPieces =>
480+
_locationPieces ??= Set.from(element.location
481+
.toString()
482+
.split(locationSplitter)
483+
.where((s) => s.isNotEmpty));
483484

484485
Set<String> get features {
485486
var allFeatures = <String>{};
@@ -648,81 +649,13 @@ abstract class ModelElement extends Canonicalization
648649
if (!_canonicalLibraryIsSet) {
649650
// This is not accurate if we are constructing the Package.
650651
assert(packageGraph.allLibrariesAdded);
651-
// Since we're looking for a library, find the [Element] immediately
652-
// contained by a [CompilationUnitElement] in the tree.
653-
var topLevelElement = element;
654-
while (topLevelElement != null &&
655-
topLevelElement.enclosingElement is! LibraryElement &&
656-
topLevelElement.enclosingElement is! CompilationUnitElement &&
657-
topLevelElement.enclosingElement != null) {
658-
topLevelElement = topLevelElement.enclosingElement;
659-
}
660652

661653
// Privately named elements can never have a canonical library, so
662654
// just shortcut them out.
663655
if (!utils.hasPublicName(element)) {
664656
_canonicalLibrary = null;
665-
} else if (definingLibrary != null &&
666-
!packageGraph.localPublicLibraries.contains(definingLibrary)) {
667-
var candidateLibraries = definingLibrary.exportedInLibraries
668-
?.where((l) =>
669-
l.isPublic &&
670-
l.package.documentedWhere != DocumentLocation.missing)
671-
?.toList();
672-
673-
if (candidateLibraries != null) {
674-
candidateLibraries = candidateLibraries.where((l) {
675-
var lookup =
676-
l.element.exportNamespace.definedNames[topLevelElement?.name];
677-
if (lookup is PropertyAccessorElement) {
678-
lookup = (lookup as PropertyAccessorElement).variable;
679-
}
680-
if (topLevelElement == lookup) return true;
681-
return false;
682-
}).toList();
683-
684-
// Avoid claiming canonicalization for elements outside of this element's
685-
// defining package.
686-
// TODO(jcollins-g): Make the else block unconditional.
687-
if (candidateLibraries.isNotEmpty &&
688-
!candidateLibraries
689-
.any((l) => l.package == definingLibrary.package)) {
690-
warn(PackageWarning.reexportedPrivateApiAcrossPackages,
691-
message: definingLibrary.package.fullyQualifiedName,
692-
referredFrom: candidateLibraries);
693-
} else {
694-
candidateLibraries
695-
.removeWhere((l) => l.package != definingLibrary.package);
696-
}
697-
698-
// Start with our top-level element.
699-
var warnable =
700-
ModelElement.fromElement(topLevelElement, packageGraph);
701-
if (candidateLibraries.length > 1) {
702-
// Heuristic scoring to determine which library a human likely
703-
// considers this element to be primarily 'from', and therefore,
704-
// canonical. Still warn if the heuristic isn't that confident.
705-
var scoredCandidates =
706-
warnable.scoreCanonicalCandidates(candidateLibraries);
707-
candidateLibraries =
708-
scoredCandidates.map((s) => s.library).toList();
709-
var secondHighestScore =
710-
scoredCandidates[scoredCandidates.length - 2].score;
711-
var highestScore = scoredCandidates.last.score;
712-
var confidence = highestScore - secondHighestScore;
713-
var message =
714-
'${candidateLibraries.map((l) => l.name)} -> ${candidateLibraries.last.name} (confidence ${confidence.toStringAsPrecision(4)})';
715-
716-
if (confidence < config.ambiguousReexportScorerMinConfidence) {
717-
warnable.warn(PackageWarning.ambiguousReexport,
718-
message: message,
719-
extendedDebug: scoredCandidates.map((s) => '$s'));
720-
}
721-
}
722-
if (candidateLibraries.isNotEmpty) {
723-
_canonicalLibrary = candidateLibraries.last;
724-
}
725-
}
657+
} else if (!packageGraph.localPublicLibraries.contains(definingLibrary)) {
658+
_canonicalLibrary = _searchForCanonicalLibrary();
726659
} else {
727660
_canonicalLibrary = definingLibrary;
728661
}
@@ -743,6 +676,79 @@ abstract class ModelElement extends Canonicalization
743676
return _canonicalLibrary;
744677
}
745678

679+
Library _searchForCanonicalLibrary() {
680+
var thisAndExported = definingLibrary.exportedInLibraries;
681+
682+
if (thisAndExported == null) {
683+
return null;
684+
}
685+
686+
// Since we're looking for a library, find the [Element] immediately
687+
// contained by a [CompilationUnitElement] in the tree.
688+
var topLevelElement = element;
689+
while (topLevelElement != null &&
690+
topLevelElement.enclosingElement is! LibraryElement &&
691+
topLevelElement.enclosingElement is! CompilationUnitElement &&
692+
topLevelElement.enclosingElement != null) {
693+
topLevelElement = topLevelElement.enclosingElement;
694+
}
695+
696+
var candidateLibraries = thisAndExported
697+
.where((l) =>
698+
l.isPublic && l.package.documentedWhere != DocumentLocation.missing)
699+
.where((l) {
700+
var lookup =
701+
l.element.exportNamespace.definedNames[topLevelElement?.name];
702+
if (lookup is PropertyAccessorElement) {
703+
lookup = (lookup as PropertyAccessorElement).variable;
704+
}
705+
return topLevelElement == lookup;
706+
}).toList();
707+
708+
// Avoid claiming canonicalization for elements outside of this element's
709+
// defining package.
710+
// TODO(jcollins-g): Make the else block unconditional.
711+
if (candidateLibraries.isNotEmpty &&
712+
!candidateLibraries.any((l) => l.package == definingLibrary.package)) {
713+
warn(PackageWarning.reexportedPrivateApiAcrossPackages,
714+
message: definingLibrary.package.fullyQualifiedName,
715+
referredFrom: candidateLibraries);
716+
} else {
717+
candidateLibraries
718+
.removeWhere((l) => l.package != definingLibrary.package);
719+
}
720+
721+
if (candidateLibraries.isEmpty) {
722+
return null;
723+
}
724+
if (candidateLibraries.length == 1) {
725+
return candidateLibraries.single;
726+
}
727+
728+
// Start with our top-level element.
729+
var warnable = ModelElement.fromElement(topLevelElement, packageGraph);
730+
// Heuristic scoring to determine which library a human likely
731+
// considers this element to be primarily 'from', and therefore,
732+
// canonical. Still warn if the heuristic isn't that confident.
733+
var scoredCandidates =
734+
warnable.scoreCanonicalCandidates(candidateLibraries);
735+
candidateLibraries = scoredCandidates.map((s) => s.library).toList();
736+
var secondHighestScore =
737+
scoredCandidates[scoredCandidates.length - 2].score;
738+
var highestScore = scoredCandidates.last.score;
739+
var confidence = highestScore - secondHighestScore;
740+
741+
if (confidence < config.ambiguousReexportScorerMinConfidence) {
742+
var libraryNames = candidateLibraries.map((l) => l.name);
743+
var message = '$libraryNames -> ${candidateLibraries.last.name} '
744+
'(confidence ${confidence.toStringAsPrecision(4)})';
745+
warnable.warn(PackageWarning.ambiguousReexport,
746+
message: message, extendedDebug: scoredCandidates.map((s) => '$s'));
747+
}
748+
749+
return candidateLibraries.last;
750+
}
751+
746752
@override
747753
bool get isCanonical {
748754
if (!isPublic) return false;
@@ -1072,6 +1078,9 @@ abstract class ModelElement extends Canonicalization
10721078

10731079
String computeDocumentationComment() => element.documentationComment;
10741080

1081+
/// The documentation comment on the Element may be null, so memoization
1082+
/// cannot rely on the null-ness of [_documentationComment], it must be
1083+
/// more explicit.
10751084
bool _documentationCommentComputed = false;
10761085
String _documentationComment;
10771086

lib/src/model/package.dart

+17-12
Original file line numberDiff line numberDiff line change
@@ -171,22 +171,27 @@ class Package extends LibraryContainer
171171
return _isLocal;
172172
}
173173

174+
/* late */ DocumentLocation _documentedWhere;
175+
174176
DocumentLocation get documentedWhere {
175-
if (isLocal) {
176-
if (isPublic) {
177-
return DocumentLocation.local;
178-
} else {
179-
// Possible if excludes result in a "documented" package not having
180-
// any actual documentation.
181-
return DocumentLocation.missing;
182-
}
183-
} else {
184-
if (config.linkToRemote && config.linkToUrl.isNotEmpty && isPublic) {
185-
return DocumentLocation.remote;
177+
if (_documentedWhere == null) {
178+
if (isLocal) {
179+
if (isPublic) {
180+
_documentedWhere = DocumentLocation.local;
181+
} else {
182+
// Possible if excludes result in a "documented" package not having
183+
// any actual documentation.
184+
_documentedWhere = DocumentLocation.missing;
185+
}
186186
} else {
187-
return DocumentLocation.missing;
187+
if (config.linkToRemote && config.linkToUrl.isNotEmpty && isPublic) {
188+
_documentedWhere = DocumentLocation.remote;
189+
} else {
190+
_documentedWhere = DocumentLocation.missing;
191+
}
188192
}
189193
}
194+
return _documentedWhere;
190195
}
191196

192197
@override

lib/src/model/package_graph.dart

+14-19
Original file line numberDiff line numberDiff line change
@@ -68,24 +68,23 @@ class PackageGraph {
6868
specialClasses = SpecialClasses();
6969
// Go through docs of every ModelElement in package to pre-build the macros
7070
// index. Uses toList() in order to get all the precaching on the stack.
71-
var precacheFutures = precacheLocalDocs().toList();
72-
for (var f in precacheFutures) {
73-
await f;
74-
}
71+
await Future.wait(precacheLocalDocs());
7572
_localDocumentationBuilt = true;
7673

7774
// Scan all model elements to insure that interceptor and other special
7875
// objects are found.
7976
// After the allModelElements traversal to be sure that all packages
8077
// are picked up.
81-
documentedPackages.toList().forEach((package) {
78+
for (var package in documentedPackages) {
8279
package.libraries.sort((a, b) => compareNatural(a.name, b.name));
83-
package.libraries.forEach((library) {
80+
for (var library in package.libraries) {
8481
library.allClasses.forEach(_addToImplementors);
8582
_extensions.addAll(library.extensions);
86-
});
87-
});
88-
_implementors.values.forEach((l) => l.sort());
83+
}
84+
}
85+
for (var l in _implementors.values) {
86+
l.sort();
87+
}
8988
allImplementorsAdded = true;
9089
allExtensionsAdded = true;
9190

@@ -574,18 +573,14 @@ class PackageGraph {
574573
}
575574
}
576575

577-
if (c.mixins.isNotEmpty) {
578-
c.mixins.forEach((t) {
579-
_checkAndAddClass(t.element, c);
580-
});
576+
for (var type in c.mixins) {
577+
_checkAndAddClass(type.element, c);
581578
}
582579
if (c.supertype != null) {
583580
_checkAndAddClass(c.supertype.element, c);
584581
}
585-
if (c.interfaces.isNotEmpty) {
586-
c.interfaces.forEach((t) {
587-
_checkAndAddClass(t.element, c);
588-
});
582+
for (var type in c.interfaces) {
583+
_checkAndAddClass(type.element, c);
589584
}
590585
}
591586

@@ -908,8 +903,8 @@ class PackageGraph {
908903
List<ModelElement> _allCanonicalModelElements;
909904

910905
Iterable<ModelElement> get allCanonicalModelElements {
911-
return (_allCanonicalModelElements ??=
912-
allLocalModelElements.where((e) => e.isCanonical).toList());
906+
return _allCanonicalModelElements ??=
907+
allLocalModelElements.where((e) => e.isCanonical).toList();
913908
}
914909

915910
String getMacro(String name) {

0 commit comments

Comments
 (0)