Skip to content

Commit 572bc31

Browse files
authored
Implemented a number of performance and memory optimizations (#10816)
* Implemented a number of performance and memory optimizations: * Made implicitImports and filteredImplicitImports fields of ImportResult optional to eliminate the need to create empty maps. * Avoided creating a new diagnostic rule set for every source file unless it overrides the default rule set. * Limited memoization of file and web URIs; we were previously allocating and caching an unlimited number of URIs which consumed 100's of Mb of space on large project. * Changed tokenizer to forcibly clone the value of a string literal so we don't retain a reference to the entire file's text contents. * Fixed regression caught by mypy_primer.
1 parent 2d1aaf0 commit 572bc31

14 files changed

Lines changed: 61 additions & 39 deletions

File tree

packages/pyright-internal/src/analyzer/binder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ export class Binder extends ParseTreeWalker {
400400
if (importResult.isNamespacePackage && node.parent?.nodeType === ParseNodeType.ImportFrom) {
401401
if (
402402
node.parent.d.imports.every((importAs) => {
403-
const implicitImport = importResult.filteredImplicitImports.get(importAs.d.name.d.value);
403+
const implicitImport = importResult.filteredImplicitImports?.get(importAs.d.name.d.value);
404404
return !!implicitImport?.pyTypedInfo;
405405
})
406406
) {
@@ -4168,7 +4168,7 @@ export class Binder extends ParseTreeWalker {
41684168
}
41694169

41704170
private _addImplicitImportsToLoaderActions(importResult: ImportResult, loaderActions: ModuleLoaderActions) {
4171-
importResult.filteredImplicitImports.forEach((implicitImport) => {
4171+
importResult.filteredImplicitImports?.forEach((implicitImport) => {
41724172
const existingLoaderAction = loaderActions.implicitImports
41734173
? loaderActions.implicitImports.get(implicitImport.name)
41744174
: undefined;

packages/pyright-internal/src/analyzer/commentUtils.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,11 @@ export function getFileLevelDirectives(
4545
diagnostics: CommentDiagnostic[]
4646
): DiagnosticRuleSet {
4747
let ruleSet = cloneDiagnosticRuleSet(defaultRuleSet);
48+
let isModified = false;
4849

4950
if (useStrict) {
5051
_applyStrictRules(ruleSet);
52+
isModified = true;
5153
}
5254

5355
for (let i = 0; i < tokens.count; i++) {
@@ -65,11 +67,13 @@ export function getFileLevelDirectives(
6567
};
6668

6769
ruleSet = _parsePyrightComment(value, textRange, isCommentOnOwnLine, ruleSet, diagnostics);
70+
isModified = true;
6871
}
6972
}
7073
}
7174

72-
return ruleSet;
75+
// If we didn't make any modifications, use the default rule set to save memory.
76+
return isModified ? ruleSet : defaultRuleSet;
7377
}
7478

7579
function _applyStrictRules(ruleSet: DiagnosticRuleSet) {

packages/pyright-internal/src/analyzer/importResolver.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -442,8 +442,6 @@ export class ImportResolver {
442442
this._cachedPythonSearchPaths = { paths: Array.from(new Set(paths)), failureInfo: info };
443443
}
444444

445-
// Make sure we cache the logs as well so we can find out why search path failed.
446-
importFailureInfo.push(...this._cachedPythonSearchPaths.failureInfo);
447445
return this._cachedPythonSearchPaths.paths;
448446
}
449447

@@ -791,15 +789,15 @@ export class ImportResolver {
791789
): ImportResult {
792790
if (importedSymbols === undefined) {
793791
const newImportResult = Object.assign({}, importResult);
794-
newImportResult.filteredImplicitImports = new Map<string, ImplicitImport>();
792+
newImportResult.filteredImplicitImports = undefined;
795793
return newImportResult;
796794
}
797795

798-
if (importedSymbols.size === 0) {
796+
if (importedSymbols === undefined || importedSymbols.size === 0) {
799797
return importResult;
800798
}
801799

802-
if (importResult.implicitImports.size === 0) {
800+
if (importResult.implicitImports === undefined || importResult.implicitImports.size === 0) {
803801
return importResult;
804802
}
805803

@@ -823,7 +821,7 @@ export class ImportResolver {
823821
importingModuleName: string,
824822
dirPath: Uri,
825823
exclusions: Uri[]
826-
): Map<string, ImplicitImport> {
824+
): Map<string, ImplicitImport> | undefined {
827825
const implicitImportMap = new Map<string, ImplicitImport>();
828826

829827
// Enumerate all of the files and directories in the path, expanding links.
@@ -911,7 +909,7 @@ export class ImportResolver {
911909
}
912910
}
913911

914-
return implicitImportMap;
912+
return implicitImportMap.size > 0 ? implicitImportMap : undefined;
915913
}
916914

917915
private _resolveImportStrict(
@@ -935,8 +933,8 @@ export class ImportResolver {
935933
importType: ImportType.Local,
936934
isStubFile: false,
937935
isNativeLib: false,
938-
implicitImports: new Map<string, ImplicitImport>(),
939-
filteredImplicitImports: new Map<string, ImplicitImport>(),
936+
implicitImports: undefined,
937+
filteredImplicitImports: undefined,
940938
nonStubImportResult: undefined,
941939
};
942940

@@ -1352,7 +1350,7 @@ export class ImportResolver {
13521350
let isStubPackage = false;
13531351
let isStubFile = false;
13541352
let isNativeLib = false;
1355-
let implicitImports = new Map<string, ImplicitImport>();
1353+
let implicitImports: Map<string, ImplicitImport> | undefined;
13561354
let packageDirectory: Uri | undefined;
13571355
let pyTypedInfo: PyTypedInfo | undefined;
13581356

@@ -1553,13 +1551,13 @@ export class ImportResolver {
15531551
// are all satisfied by submodules (as listed in the implicit imports).
15541552
private _isNamespacePackageResolved(
15551553
moduleDescriptor: ImportedModuleDescriptor,
1556-
implicitImports: Map<string, ImplicitImport>
1554+
implicitImports: Map<string, ImplicitImport> | undefined
15571555
) {
15581556
if (moduleDescriptor.importedSymbols) {
1559-
if (!Array.from(moduleDescriptor.importedSymbols.keys()).some((symbol) => implicitImports.has(symbol))) {
1557+
if (!Array.from(moduleDescriptor.importedSymbols.keys()).some((symbol) => implicitImports?.has(symbol))) {
15601558
return false;
15611559
}
1562-
} else if (implicitImports.size === 0) {
1560+
} else if (!implicitImports || implicitImports.size === 0) {
15631561
return false;
15641562
}
15651563
return true;
@@ -2377,8 +2375,8 @@ export class ImportResolver {
23772375
importType: ImportType.Local,
23782376
isStubFile: false,
23792377
isNativeLib: false,
2380-
implicitImports: [],
2381-
filteredImplicitImports: [],
2378+
implicitImports: undefined,
2379+
filteredImplicitImports: undefined,
23822380
nonStubImportResult: undefined,
23832381
};
23842382
}

packages/pyright-internal/src/analyzer/importResult.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,12 @@ export interface ImportResult {
8888
// List of files within the final resolved path that are implicitly
8989
// imported as part of the package - used for both traditional and
9090
// namespace packages.
91-
implicitImports: Map<string, ImplicitImport>;
91+
implicitImports?: Map<string, ImplicitImport>;
9292

9393
// Implicit imports that have been filtered to include only
9494
// those symbols that are explicitly imported in a "from x import y"
9595
// statement.
96-
filteredImplicitImports: Map<string, ImplicitImport>;
96+
filteredImplicitImports?: Map<string, ImplicitImport>;
9797

9898
// If resolved from a type hint (.pyi), then store the import result
9999
// from .py here.

packages/pyright-internal/src/analyzer/importStatementUtils.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -678,10 +678,12 @@ function _processImportFromNode(
678678
if (includeImplicitImports && importResult) {
679679
localImports.implicitImports = localImports.implicitImports ?? new Map<string, ImportFromAsNode>();
680680

681-
for (const implicitImport of importResult.implicitImports.values()) {
682-
const importFromAs = node.d.imports.find((i) => i.d.name.d.value === implicitImport.name);
683-
if (importFromAs) {
684-
localImports.implicitImports.set(implicitImport.uri.key, importFromAs);
681+
if (importResult.implicitImports) {
682+
for (const implicitImport of importResult.implicitImports.values()) {
683+
const importFromAs = node.d.imports.find((i) => i.d.name.d.value === implicitImport.name);
684+
if (importFromAs) {
685+
localImports.implicitImports.set(implicitImport.uri.key, importFromAs);
686+
}
685687
}
686688
}
687689
}

packages/pyright-internal/src/analyzer/program.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,7 @@ export class Program {
13391339
thirdPartyImportAllowed = true;
13401340
} else if (
13411341
importResult.isNamespacePackage &&
1342+
importResult.filteredImplicitImports &&
13421343
Array.from(importResult.filteredImplicitImports.values()).some(
13431344
(implicitImport) => !!implicitImport.pyTypedInfo
13441345
)
@@ -1434,7 +1435,7 @@ export class Program {
14341435
}
14351436
}
14361437

1437-
importResult.filteredImplicitImports.forEach((implicitImport) => {
1438+
importResult.filteredImplicitImports?.forEach((implicitImport) => {
14381439
if (this._isImportAllowed(sourceFileInfo, importResult, implicitImport.isStubFile)) {
14391440
if (!implicitImport.isNativeLib) {
14401441
const thirdPartyTypeInfo = getThirdPartyImportInfo(importResult);

packages/pyright-internal/src/analyzer/service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1407,7 +1407,7 @@ export class AnalyzerService {
14071407
}
14081408

14091409
// Add the implicit import paths.
1410-
importResult.filteredImplicitImports.forEach((implicitImport) => {
1410+
importResult.filteredImplicitImports?.forEach((implicitImport) => {
14111411
if (ImportResolver.isSupportedImportSourceFile(implicitImport.uri)) {
14121412
filesToImport.push(implicitImport.uri);
14131413
}

packages/pyright-internal/src/analyzer/sourceFile.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ export class SourceFile {
455455
this._writableData.parsedFileContents = undefined;
456456
this._writableData.moduleSymbolTable = undefined;
457457
this._writableData.isBindingNeeded = true;
458+
this._writableData.imports = [];
458459
}
459460

460461
markDirty(): void {

packages/pyright-internal/src/analyzer/typeEvaluator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,6 @@ export function createTypeEvaluator(
643643
wrapWithLogger: LogWrapper
644644
): TypeEvaluator {
645645
const symbolResolutionStack: SymbolResolutionStackEntry[] = [];
646-
const asymmetricAccessorAssignmentCache = new Set<number>();
647646
const speculativeTypeTracker = new SpeculativeTypeTracker();
648647
const suppressedNodeStack: SuppressedNodeStackEntry[] = [];
649648
const assignClassToSelfStack: AssignClassToSelfInfo[] = [];
@@ -653,6 +652,7 @@ export function createTypeEvaluator(
653652
let typeCache = new Map<number, TypeCacheEntry>();
654653
let effectiveTypeCache = new Map<number, Map<string, EffectiveTypeResult>>();
655654
let expectedTypeCache = new Map<number, Type>();
655+
let asymmetricAccessorAssignmentCache = new Set<number>();
656656
let deferredClassCompletions: DeferredClassCompletion[] = [];
657657
let cancellationToken: CancellationToken | undefined;
658658
let printExpressionSpaceCount = 0;
@@ -708,6 +708,7 @@ export function createTypeEvaluator(
708708
typeCache = new Map<number, TypeCacheEntry>();
709709
effectiveTypeCache = new Map<number, Map<string, EffectiveTypeResult>>();
710710
expectedTypeCache = new Map<number, Type>();
711+
asymmetricAccessorAssignmentCache = new Set<number>();
711712
}
712713

713714
function readTypeCacheEntry(node: ParseNode) {

packages/pyright-internal/src/common/fullAccessHost.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ import { PythonPlatform } from './configOptions';
1515
import { assertNever } from './debug';
1616
import { HostKind, NoAccessHost, ScriptOutput } from './host';
1717
import { getAnyExtensionFromPath, normalizePath } from './pathUtils';
18+
import { terminateChild } from './processUtils';
1819
import { PythonVersion } from './pythonVersion';
1920
import { ServiceKeys } from './serviceKeys';
2021
import { ServiceProvider } from './serviceProvider';
2122
import { Uri } from './uri/uri';
2223
import { isDirectory } from './uri/uriUtils';
23-
import { terminateChild } from './processUtils';
2424

2525
// preventLocalImports removes the working directory from sys.path.
2626
// The -c flag adds it automatically, which can allow some stdlib

0 commit comments

Comments
 (0)