From 8d3b6ce54ccb09dd970cd45fc3cded4a35beac31 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Mon, 8 Jan 2024 18:55:48 +0000 Subject: [PATCH] Use flattenedToList/flattenedToSet instead of generic expand(). It is faster. [expand: 102] [flattened: 131] [flattened2: 37] import 'package:collection/collection.dart'; main() { const outerLength = 100; const innerLength = 10; final listOfLists = List.generate(outerLength, (i) { return List.generate(innerLength, (j) => i * innerLength + j); }); for (var i = 0; i < 10; i++) { f(listOfLists); } } void f(List> listOfLists) { const repeatCount = 10000; { final timer = Stopwatch()..start(); for (var i = 0; i < repeatCount; i++) { listOfLists.expand((e) => e).toList(); } print('[expand: ${timer.elapsedMilliseconds}]'); } { final timer = Stopwatch()..start(); for (var i = 0; i < repeatCount; i++) { listOfLists.flattened.toList(); } print('[flattened: ${timer.elapsedMilliseconds}]'); } { final timer = Stopwatch()..start(); for (var i = 0; i < repeatCount; i++) { listOfLists.flattened2.toList(); } print('[flattened2: ${timer.elapsedMilliseconds}]'); } } extension ListListExtensions on List> { Iterable get flattened2 { return [ for (final elements in this) ...elements, ]; } } Change-Id: I0bf9dc0c8735fe62aab69cbce276254e2db110f9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/345202 Reviewed-by: Jacob Richman Reviewed-by: Brian Wilkerson Reviewed-by: Phil Quitslund Commit-Queue: Konstantin Shcheglov --- .../lsp/handlers/code_actions/plugins.dart | 4 +-- .../lsp/handlers/handler_semantic_tokens.dart | 4 +-- pkg/analysis_server/lib/src/lsp/mapping.dart | 4 +-- .../lib/src/lsp/semantic_tokens/legend.dart | 5 ++-- .../lib/src/operation/operation_analysis.dart | 4 +-- .../fix/data_driven/transform_set_parser.dart | 6 ++--- .../lib/src/utilities/import_analyzer.dart | 4 +-- .../lib/src/dart/analysis/driver.dart | 3 ++- .../lib/src/dart/analysis/library_graph.dart | 2 +- .../src/dart/element/generic_inferrer.dart | 3 ++- pkg/analyzer/lib/src/fasta/ast_builder.dart | 3 ++- .../lib/src/summary2/library_builder.dart | 2 +- .../src/utilities/extensions/collection.dart | 20 ++++++++++++++ .../element/inheritance_manager3_test.dart | 3 ++- .../utilities/extensions/collection_test.dart | 26 +++++++++++++++++++ 15 files changed, 72 insertions(+), 21 deletions(-) diff --git a/pkg/analysis_server/lib/src/lsp/handlers/code_actions/plugins.dart b/pkg/analysis_server/lib/src/lsp/handlers/code_actions/plugins.dart index 30496025060..ca3f9fd2842 100644 --- a/pkg/analysis_server/lib/src/lsp/handlers/code_actions/plugins.dart +++ b/pkg/analysis_server/lib/src/lsp/handlers/code_actions/plugins.dart @@ -8,6 +8,7 @@ import 'package:analysis_server/lsp_protocol/protocol.dart'; import 'package:analysis_server/src/lsp/handlers/code_actions/abstract_code_actions_producer.dart'; import 'package:analysis_server/src/lsp/mapping.dart'; import 'package:analyzer/src/dart/analysis/driver.dart'; +import 'package:analyzer/src/utilities/extensions/collection.dart'; import 'package:analyzer_plugin/protocol/protocol.dart' as plugin; import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin; import 'package:analyzer_plugin/src/protocol/protocol_internal.dart' as plugin; @@ -60,8 +61,7 @@ class PluginCodeActionsProducer extends AbstractCodeActionsProducer { .map((response) => plugin.EditGetFixesResult.fromResponse(response)) .expand((response) => response.fixes) .map(_convertFixes) - .expand((fix) => fix) - .toList(); + .flattenedToList; } @override diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_semantic_tokens.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_semantic_tokens.dart index e2d78b4d3b9..e2f0acd1bbf 100644 --- a/pkg/analysis_server/lib/src/lsp/handlers/handler_semantic_tokens.dart +++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_semantic_tokens.dart @@ -13,6 +13,7 @@ import 'package:analysis_server/src/lsp/registration/feature_registration.dart'; import 'package:analysis_server/src/lsp/semantic_tokens/encoder.dart'; import 'package:analysis_server/src/lsp/semantic_tokens/legend.dart'; import 'package:analyzer/source/source_range.dart'; +import 'package:analyzer/src/utilities/extensions/collection.dart'; import 'package:analyzer_plugin/protocol/protocol_common.dart'; typedef StaticOptions @@ -65,8 +66,7 @@ abstract class AbstractSemanticTokensHandler return toSourceRangeNullable(lineInfo, range).mapResult((range) async { final serverTokens = await getServerResult(path, range); - final pluginHighlightRegions = - getPluginResults(path).expand((results) => results).toList(); + final pluginHighlightRegions = getPluginResults(path).flattenedToList; if (token.isCancellationRequested) { return cancelled(); diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart index c6ff9e96cd2..74670340b42 100644 --- a/pkg/analysis_server/lib/src/lsp/mapping.dart +++ b/pkg/analysis_server/lib/src/lsp/mapping.dart @@ -27,6 +27,7 @@ import 'package:analyzer/source/source_range.dart' as server; import 'package:analyzer/src/dart/analysis/search.dart' as server show DeclarationKind; import 'package:analyzer/src/error/codes.dart'; +import 'package:analyzer/src/utilities/extensions/collection.dart'; import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin; import 'package:collection/collection.dart'; import 'package:path/path.dart' as path; @@ -1165,8 +1166,7 @@ List toHighlights( .map((occurrence) => occurrence.offsets.map((offset) => lsp.DocumentHighlight( range: toRange(lineInfo, offset, occurrence.length)))) - .expand((occurrences) => occurrences) - .toSet() + .flattenedToSet .toList(); } diff --git a/pkg/analysis_server/lib/src/lsp/semantic_tokens/legend.dart b/pkg/analysis_server/lib/src/lsp/semantic_tokens/legend.dart index a79a488da7c..366015c5df3 100644 --- a/pkg/analysis_server/lib/src/lsp/semantic_tokens/legend.dart +++ b/pkg/analysis_server/lib/src/lsp/semantic_tokens/legend.dart @@ -7,6 +7,7 @@ import 'dart:math' as math; import 'package:analysis_server/lsp_protocol/protocol.dart'; import 'package:analysis_server/src/lsp/constants.dart'; import 'package:analysis_server/src/lsp/semantic_tokens/mapping.dart'; +import 'package:analyzer/src/utilities/extensions/collection.dart'; import 'package:meta/meta.dart'; final semanticTokenLegend = SemanticTokenLegendLookup(); @@ -33,8 +34,8 @@ class SemanticTokenLegendLookup { _usedTokenTypes = Set.of(highlightRegionTokenTypes.values .followedBy(CustomSemanticTokenTypes.values)) .toList(); - _usedTokenModifiers = Set.of(highlightRegionTokenModifiers.values - .expand((v) => v) + _usedTokenModifiers = Set.of(highlightRegionTokenModifiers + .values.flattenedToList .followedBy(CustomSemanticTokenModifiers.values)) .toList(); diff --git a/pkg/analysis_server/lib/src/operation/operation_analysis.dart b/pkg/analysis_server/lib/src/operation/operation_analysis.dart index c81de49a4fb..10cffda5d5a 100644 --- a/pkg/analysis_server/lib/src/operation/operation_analysis.dart +++ b/pkg/analysis_server/lib/src/operation/operation_analysis.dart @@ -14,6 +14,7 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/exception/exception.dart'; import 'package:analyzer/source/line_info.dart'; import 'package:analyzer/src/generated/source.dart'; +import 'package:analyzer/src/utilities/extensions/collection.dart'; Future scheduleImplementedNotification( LegacyAnalysisServer server, Iterable files) async { @@ -42,8 +43,7 @@ void sendAnalysisNotificationAnalyzedFiles(LegacyAnalysisServer server) { _sendNotification(server, () { var analyzedFiles = server.driverMap.values .map((driver) => driver.knownFiles) - .expand((files) => files) - .toSet(); + .flattenedToSet; // Exclude *.yaml files because IDEA Dart plugin attempts to index // all the files in folders which contain analyzed files. diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart index d910bcbe730..8dfa0f36ff8 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart @@ -22,6 +22,7 @@ import 'package:analysis_server/src/services/correction/fix/data_driven/variable import 'package:analysis_server/src/services/refactoring/framework/formal_parameter.dart'; import 'package:analyzer/error/listener.dart'; import 'package:analyzer/src/util/yaml.dart'; +import 'package:analyzer/src/utilities/extensions/collection.dart'; import 'package:analyzer/src/utilities/extensions/string.dart'; import 'package:collection/collection.dart'; import 'package:yaml/yaml.dart'; @@ -350,9 +351,8 @@ class TransformSetParser { var firstEntry = entries.firstOrNull; if (firstEntry == null) { if (required) { - var validKeysList = translators.keys - .expand((keys) => keys) - .quotedAndCommaSeparatedWithOr; + var validKeysList = + translators.keys.flattenedToList.quotedAndCommaSeparatedWithOr; _reportError(TransformSetErrorCode.missingOneOfMultipleKeys, errorNode, [validKeysList]); } diff --git a/pkg/analysis_server/lib/src/utilities/import_analyzer.dart b/pkg/analysis_server/lib/src/utilities/import_analyzer.dart index 3e4e47909fd..90ecb647af1 100644 --- a/pkg/analysis_server/lib/src/utilities/import_analyzer.dart +++ b/pkg/analysis_server/lib/src/utilities/import_analyzer.dart @@ -8,6 +8,7 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/visitor.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/source/source_range.dart'; +import 'package:analyzer/src/utilities/extensions/collection.dart'; /// A utility class used to analyze a library from which some set of /// declarations are being moved in order to compute the set of changes needed @@ -298,8 +299,7 @@ class _ReferenceFinder extends RecursiveAstVisitor { // Extensions can be used without a prefix, so we can use any import that // brings in the extension. if (import == null && prefix == null && element is ExtensionElement) { - import = _importsByPrefix.values - .expand((imports) => imports) + import = _importsByPrefix.values.flattenedToList .where((import) => // Because we don't know what prefix we're looking for (any is // allowed), use the imports own prefix when checking for the diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart index b65a669e983..d8634656a0f 100644 --- a/pkg/analyzer/lib/src/dart/analysis/driver.dart +++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart @@ -51,6 +51,7 @@ import 'package:analyzer/src/summary2/macro.dart'; import 'package:analyzer/src/summary2/package_bundle_format.dart'; import 'package:analyzer/src/util/file_paths.dart' as file_paths; import 'package:analyzer/src/util/performance/operation_performance.dart'; +import 'package:analyzer/src/utilities/extensions/collection.dart'; import 'package:analyzer/src/utilities/uri_cache.dart'; /// This class computes [AnalysisResult]s for Dart files. @@ -2558,7 +2559,7 @@ class _DiscoverAvailableFilesTask { // Discover files in package/lib folders. var packageMap = driver._sourceFactory.packageMap; if (packageMap != null) { - folderIterator = packageMap.values.expand((f) => f).iterator; + folderIterator = packageMap.values.flattenedToList.iterator; } else { folderIterator = [].iterator; } diff --git a/pkg/analyzer/lib/src/dart/analysis/library_graph.dart b/pkg/analyzer/lib/src/dart/analysis/library_graph.dart index d74630d581f..c1113f8fc68 100644 --- a/pkg/analyzer/lib/src/dart/analysis/library_graph.dart +++ b/pkg/analyzer/lib/src/dart/analysis/library_graph.dart @@ -149,7 +149,7 @@ class _LibraryNode extends graph.Node<_LibraryNode> { .whereType() .map((export) => export.exportedLibrary), ]) - .expand((libraries) => libraries) + .flattenedToList .whereNotNull() .toSet(); diff --git a/pkg/analyzer/lib/src/dart/element/generic_inferrer.dart b/pkg/analyzer/lib/src/dart/element/generic_inferrer.dart index a767bb3aa57..8801ec5eea0 100644 --- a/pkg/analyzer/lib/src/dart/element/generic_inferrer.dart +++ b/pkg/analyzer/lib/src/dart/element/generic_inferrer.dart @@ -27,6 +27,7 @@ import 'package:analyzer/src/dart/element/type_schema.dart'; import 'package:analyzer/src/dart/element/type_system.dart'; import 'package:analyzer/src/error/codes.dart' show CompileTimeErrorCode, WarningCode; +import 'package:analyzer/src/utilities/extensions/collection.dart'; import 'package:meta/meta.dart'; /// Tracks upper and lower type bounds for a set of type parameters. @@ -491,7 +492,7 @@ class GenericInferrer { .values .where((l) => l.every((c) => c.isSatisfiedBy(_typeSystem, inferred)) == expected) - .expand((i) => i); + .flattenedToList; String unsatisfied = _formatConstraints(isSatisfied(false)); String satisfied = _formatConstraints(isSatisfied(true)); diff --git a/pkg/analyzer/lib/src/fasta/ast_builder.dart b/pkg/analyzer/lib/src/fasta/ast_builder.dart index fb28e840502..d4c6a342dd5 100644 --- a/pkg/analyzer/lib/src/fasta/ast_builder.dart +++ b/pkg/analyzer/lib/src/fasta/ast_builder.dart @@ -68,6 +68,7 @@ import 'package:analyzer/src/fasta/doc_comment_builder.dart'; import 'package:analyzer/src/fasta/error_converter.dart'; import 'package:analyzer/src/generated/utilities_dart.dart'; import 'package:analyzer/src/summary2/ast_binary_tokens.dart'; +import 'package:analyzer/src/utilities/extensions/collection.dart'; import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:pub_semver/pub_semver.dart'; @@ -3071,7 +3072,7 @@ class AstBuilder extends StackListener { debugEvent("SwitchBlock"); var membersList = popTypedList2>(caseCount); - var members = membersList.expand((members) => members).toList(); + var members = membersList.flattenedToList; Set labels = {}; for (var member in members) { diff --git a/pkg/analyzer/lib/src/summary2/library_builder.dart b/pkg/analyzer/lib/src/summary2/library_builder.dart index c7559b49f90..61d5563c067 100644 --- a/pkg/analyzer/lib/src/summary2/library_builder.dart +++ b/pkg/analyzer/lib/src/summary2/library_builder.dart @@ -460,7 +460,7 @@ class LibraryBuilder { } final augmentationCode = macroApplier.buildAugmentationLibraryCode( - _macroResults.expand((e) => e).toList(), + _macroResults.flattenedToList, ); if (augmentationCode == null) { return; diff --git a/pkg/analyzer/lib/src/utilities/extensions/collection.dart b/pkg/analyzer/lib/src/utilities/extensions/collection.dart index 14362d64a8b..0ba8e0c0a47 100644 --- a/pkg/analyzer/lib/src/utilities/extensions/collection.dart +++ b/pkg/analyzer/lib/src/utilities/extensions/collection.dart @@ -19,6 +19,26 @@ extension IterableExtension on Iterable { } } +extension IterableIterableExtension on Iterable> { + /// Elements of each iterable in this iterable. + /// + /// At the moment of writing, this method is `2.75` times faster than + /// `expand((e) => e)`, and `3.5` faster than `flattened` from + /// `package:collection`. + List get flattenedToList { + return [ + for (final elements in this) ...elements, + ]; + } + + /// Elements of each iterable in this iterable. + Set get flattenedToSet { + return { + for (final elements in this) ...elements, + }; + } +} + extension IterableMapEntryExtension on Iterable> { Map get mapFromEntries => Map.fromEntries(this); } diff --git a/pkg/analyzer/test/src/dart/element/inheritance_manager3_test.dart b/pkg/analyzer/test/src/dart/element/inheritance_manager3_test.dart index 35953aa1dac..d6f7cc4d958 100644 --- a/pkg/analyzer/test/src/dart/element/inheritance_manager3_test.dart +++ b/pkg/analyzer/test/src/dart/element/inheritance_manager3_test.dart @@ -8,6 +8,7 @@ import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/src/dart/element/element.dart'; import 'package:analyzer/src/dart/element/extensions.dart'; import 'package:analyzer/src/dart/element/inheritance_manager3.dart'; +import 'package:analyzer/src/utilities/extensions/collection.dart'; import 'package:collection/collection.dart'; import 'package:test/test.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; @@ -3022,7 +3023,7 @@ class _InterfacePrinter { String name, Map> map, ) { - final isEmpty = map.values.expand((elements) => elements).where((element) { + final isEmpty = map.values.flattenedToList.where((element) { if (_configuration.withObjectMembers) return true; return !element.isObjectMember; }).isEmpty; diff --git a/pkg/analyzer/test/src/utilities/extensions/collection_test.dart b/pkg/analyzer/test/src/utilities/extensions/collection_test.dart index b8b8520156f..9d824a36454 100644 --- a/pkg/analyzer/test/src/utilities/extensions/collection_test.dart +++ b/pkg/analyzer/test/src/utilities/extensions/collection_test.dart @@ -9,6 +9,7 @@ import 'package:test_reflective_loader/test_reflective_loader.dart'; main() { defineReflectiveSuite(() { defineReflectiveTests(IterableExtensionTest); + defineReflectiveTests(IterableIterableExtensionTest); defineReflectiveTests(IterableMapEntryExtensionTest); defineReflectiveTests(ListExtensionTest); }); @@ -21,6 +22,31 @@ class IterableExtensionTest { } } +@reflectiveTest +class IterableIterableExtensionTest { + test_flattenedToList() { + expect( + [ + [0], + [1, 2], + [3, 3] + ].flattenedToList, + [0, 1, 2, 3, 3], + ); + } + + test_flattenedToSet() { + expect( + [ + [0, 0], + [1, 2, 1], + [3, 3] + ].flattenedToSet, + {0, 1, 2, 3}, + ); + } +} + @reflectiveTest class IterableMapEntryExtensionTest { test_mapFromEntries() {