performance.md: extract psi_performance.md

This commit is contained in:
Yann Cébron 2022-03-08 17:14:23 +01:00
parent 37510618ac
commit c73adf5a64
6 changed files with 45 additions and 43 deletions

View File

@ -172,6 +172,7 @@
<toc-element id="psi_references.md"/> <toc-element id="psi_references.md"/>
<toc-element id="modifying_psi.md"/> <toc-element id="modifying_psi.md"/>
<toc-element id="psi_cookbook.md"/> <toc-element id="psi_cookbook.md"/>
<toc-element id="psi_performance.md"/>
<toc-element id="indexing_and_psi_stubs.md"> <toc-element id="indexing_and_psi_stubs.md">
<toc-element id="file_based_indexes.md"/> <toc-element id="file_based_indexes.md"/>
<toc-element id="stub_indexes.md"/> <toc-element id="stub_indexes.md"/>

View File

@ -0,0 +1,40 @@
[//]: # (title: PSI Performance)
<!-- Copyright 2000-2022 JetBrains s.r.o. and other contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->
## Avoid Expensive Methods of `PsiElement`
Avoid `PsiElement`'s methods which are expensive with deep trees.
`getText()` traverses the whole tree under the given element and concatenates strings, consider `textMatches()` instead.
`getTextRange()`, `getContainingFile()`, and `getProject()` traverse the tree up to the file, which can be long in very nested trees.
If you only need PSI element length, use `getTextLength()`.
File and project often can be computed once per some analysis and then stored in fields or passed via parameters.
Additionally, methods such as `getText()`, `getNode()`, or `getTextRange()`, need the AST, obtaining which can be quite an expensive operation.
See below.
## Avoid Using Many PSI Trees/Documents
Avoid loading too many parsed trees or documents into memory at the same time.
Ideally, only AST nodes from files open in the editor should be present in the memory.
Everything else, even if it's needed for resolve/highlighting purposes, can be accessed via PSI interfaces, but its implementations should [use stubs](stub_indexes.md) underneath, which are less CPU- and memory-expensive.
If stubs don't suit your case well (e.g., the information you need is large and/or very rarely needed, or you're developing a plugin for a language whose PSI you don't control), you can create a [custom index or gist](indexing_and_psi_stubs.md).
To ensure you're not loading AST accidentally, you can use [`AstLoadingFilter`](upsource:///platform/core-api/src/com/intellij/util/AstLoadingFilter.java) in production and `PsiManagerEx.setAssertOnFileLoadingFilter()` in tests.
The same applies to documents: only the ones opened in editors should be loaded.
Usually, you shouldn't need document contents (as most information can be retrieved from PSI).
If you nevertheless need documents, consider saving the information you need to provide in a [custom index or gist](indexing_and_psi_stubs.md) to get it more cheaply later.
If you still need documents, then at least ensure you load them one by one and don't hold them on strong references to let GC free the memory as quickly as possible.
## Cache Results of Heavy Computations
Method calls such as `PsiElement.getReference(s)`, `PsiReference.resolve()` (and `multiResolve()` and other equivalents) or computation of expression types, type inference results, control flow graphs, etc. can be expensive.
To avoid paying this cost several times, the result of such computation can be cached and reused.
Usually, [`CachedValue`](upsource:///platform/core-api/src/com/intellij/psi/util/CachedValue.java) works well for this purpose.
If the information you cache depends only on a subtree of the current PSI element (and nothing else: no resolve results or other files), you can cache it in a field in that `PsiElement` and drop the cache in an override of `ASTDelegatePsiElement.subtreeChanged()`.

View File

@ -34,7 +34,7 @@ The process of resolving references is distinct from parsing and is not performe
Moreover, it is not always successful. Moreover, it is not always successful.
If the code currently open in the IDE does not compile, or in other situations, it's normal for `PsiReference.resolve()` to return `null` - all code working with references must be prepared to handle that. If the code currently open in the IDE does not compile, or in other situations, it's normal for `PsiReference.resolve()` to return `null` - all code working with references must be prepared to handle that.
> Please see also _Cache results of heavy computations_ in [Working with PSI efficiently](performance.md#working-with-psi-efficiently). > Please see also [](psi_performance.md#cache-results-of-heavy-computations).
> >
{type="tip"} {type="tip"}

View File

@ -9,7 +9,7 @@ This page gives recipes for the most common operations for working with the PSI
Unlike [Developing Custom Language Plugins](custom_language_support.md), it is about working with the PSI of existing languages (such as Java). Unlike [Developing Custom Language Plugins](custom_language_support.md), it is about working with the PSI of existing languages (such as Java).
> Please see also [Working with PSI efficiently](performance.md#working-with-psi-efficiently). > Please see also [](psi_performance.md).
> >
{type="tip"} {type="tip"}

View File

@ -36,7 +36,7 @@ Still, additional optimizations are possible (for example, performing the tree w
- [Custom Language Support Tutorial: Reference Contributor](reference_contributor.md) - [Custom Language Support Tutorial: Reference Contributor](reference_contributor.md)
> To optimize `getReferences()` performance, consider implementing [`HintedReferenceHost`](upsource:///platform/core-api/src/com/intellij/psi/HintedReferenceHost.java) to provide additional hints. > To optimize `getReferences()` performance, consider implementing [`HintedReferenceHost`](upsource:///platform/core-api/src/com/intellij/psi/HintedReferenceHost.java) to provide additional hints.
> Please see also _Cache Results of Heavy Computations_ in [Working with PSI efficiently](performance.md#working-with-psi-efficiently). > Please see also [](psi_performance.md#cache-results-of-heavy-computations).
> >
{type="tip"} {type="tip"}

View File

@ -6,49 +6,10 @@
> >
{type="tip"} {type="tip"}
> See also [](indexing_and_psi_stubs.md#improving-indexing-performance) > See also [](psi_performance.md) and [](indexing_and_psi_stubs.md#improving-indexing-performance)
> >
{type="note"} {type="note"}
## Working with PSI Efficiently
#### Avoid Expensive Methods of `PsiElement`
Avoid `PsiElement`'s methods which are expensive with deep trees.
`getText()` traverses the whole tree under the given element and concatenates strings, consider `textMatches()` instead.
`getTextRange()`, `getContainingFile()`, and `getProject()` traverse the tree up to the file, which can be long in very nested trees.
If you only need PSI element length, use `getTextLength()`.
File and project often can be computed once per some analysis and then stored in fields or passed via parameters.
Additionally, methods such as `getText()`, `getNode()`, or `getTextRange()`, need the AST, obtaining which can be quite an expensive operation.
See below.
#### Avoid Using Many PSI Trees/Documents
Avoid loading too many parsed trees or documents into memory at the same time.
Ideally, only AST nodes from files open in the editor should be present in the memory.
Everything else, even if it's needed for resolve/highlighting purposes, can be accessed via PSI interfaces, but its implementations should [use stubs](stub_indexes.md) underneath, which are less CPU- and memory-expensive.
If stubs don't suit your case well (e.g., the information you need is large and/or very rarely needed, or you're developing a plugin for a language whose PSI you don't control), you can create a [custom index or gist](indexing_and_psi_stubs.md).
To ensure you're not loading AST accidentally, you can use [`AstLoadingFilter`](upsource:///platform/core-api/src/com/intellij/util/AstLoadingFilter.java) in production and `PsiManagerEx.setAssertOnFileLoadingFilter()` in tests.
The same applies to documents: only the ones opened in editors should be loaded.
Usually, you shouldn't need document contents (as most information can be retrieved from PSI).
If you nevertheless need documents, consider saving the information you need to provide in a [custom index or gist](indexing_and_psi_stubs.md) to get it more cheaply later.
If you still need documents, then at least ensure you load them one by one and don't hold them on strong references to let GC free the memory as quickly as possible.
#### Cache Results of Heavy Computations
Method calls such as `PsiElement.getReference(s)`, `PsiReference.resolve()` (and `multiResolve()` and other equivalents) or computation of expression types, type inference results, control flow graphs, etc. can be expensive.
To avoid paying this cost several times, the result of such computation can be cached and reused.
Usually, [`CachedValue`](upsource:///platform/core-api/src/com/intellij/psi/util/CachedValue.java) works well for this purpose.
If the information you cache depends only on a subtree of the current PSI element (and nothing else: no resolve results or other files), you can cache it in a field in that `PsiElement` and drop the cache in an override of `ASTDelegatePsiElement.subtreeChanged()`.
## Avoiding UI Freezes ## Avoiding UI Freezes
#### Do not Perform Long Operations in UI Thread #### Do not Perform Long Operations in UI Thread