PSI性能
PSI性能
避免使用耗时长的PsiElement方法
尽量避免使用PsiElement中某些耗时长的方法
PsiElement.getText() 方法会遍历PSI元素的子元素树并拼接字符串,耗时较长,可以考虑使用 PsiElement.textMatches()
如果PSI元素嵌套的比较深,getTextRange(), getContainingFile(), getProject() 方法会遍历所有树直到所在文件,耗时会很长。如果你只需要PsiElement的长度,可以使用 getTextLength()
getContainingFile() 和 getProject() 通常只执行一次,然后存储在成员变量中,或者通过参数进行传递。
此外,getText()、getNode() 或 getTextRange() 等方法需要使用 AST(abstract syntax tree 抽象语法结构树),耗时可能会很长,详情见下一节。
避免同时使用多个PSI树或Document
应该避免同时在内存中加载多个PSI树或Document。理想情况下,应该只把当前打开的文件生成的 AST(abstract syntax tree 抽象语法结构树) 加载进内存中。其他所有东西,即使是为了解析或高亮显示,也可以通过使用了stubs 的 PSI 接口访问,可以减少 CPU 和内存的开销。
如果 stubs 不能解决你的问题(例如:你需要的内容占用空间很大,或很少需要它,或你正在为无法控制的PSI元素开发插件),可以创建自定义索引或gist
在生产中使用 AstLoadingFilter 可以避免意外加载 AST ,在测试中可以使用 PsiManagerEx.setAssertOnFileLoadingFilter() 来避免。
对 Document 来说一样,应该只在内存中加载当前打开的文件。通常,你不需要Document 的内容,可以使用 PSI来检索你需要的元素。如果实在需要Document 内容,可以考虑把内容缓存存进自定义索引或gist 中,方便下次直接获取。如果你仍然需要 Document,至少你应该一个一个的加载并且不使用强引用(strong references),使GC能尽可能快速回收它。
缓存
执行 PsiElement.getReference(s), PsiReference.resolve(),PsiReference.multiResolve() 或computation of expression types, type inference results, control flow graphs 等耗时长的操作时,需要避免多次调用,可以缓存它们的计算结果。通常,可以使用 CachedValue
如果缓存的信息仅取决于当前 PSI 元素的子树(没有计算或其它文件),你可以把结果缓存到PsiElement实现类的成员变量里,并在 ASTDelegatePsiElement.subtreeChanged() 的重写方法里删除缓存,部分代码如下:
//成员变量缓存name
private volatile String myCachedName;
@Override
public String getName() {
String name = myCachedName;
// 如果有缓存,直接返回
if (name != null) return name;
final PsiClassStub stub = getGreenStub();
if (stub == null) {
PsiIdentifier identifier = getNameIdentifier();
name = identifier == null ? null : identifier.getText();
}
else {
name = stub.getName();
}
//没有缓存,计算后缓存 name
myCachedName = name;
return name;
}
@Override
public void subtreeChanged() {
dropCaches();
super.subtreeChanged();
}
//清除缓存
private void dropCaches() {
myCachedName = null;
}