java resolve: cache resolved qualifiers when global cache is prohibited (IDEA-219505) ^peter

when type inference is called, it's not known if the client calls inference on resolved method or enumerates all possible candidates and thus the results cant be cached; current implementation is pessimistic and prohibit all caching during inference. Thus, for long method call chains which depend on some non-trivial calculations, it may be extremely resource consuming. Let's cache all qualifiers locally: this doesn't prevent recalculation globally but works around performance problems per single call

GitOrigin-RevId: b9b42cbc50918259f5de3a81d5f3a38967c153f1
This commit is contained in:
Anna Kozlova
2019-08-19 21:53:28 +02:00
committed by intellij-monorepo-bot
parent aac4cbb7af
commit fd969c0595
3 changed files with 225 additions and 22 deletions

View File

@@ -34,7 +34,10 @@ import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.*;
import com.intellij.util.CharTable;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.NullableFunction;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
@@ -44,6 +47,7 @@ import java.util.*;
public class PsiReferenceExpressionImpl extends ExpressionPsiElement implements PsiReferenceExpression, SourceJavaCodeReference {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl");
private static final ThreadLocal<Map<PsiReferenceExpression, ResolveResult[]>> ourQualifiersCache = ThreadLocal.withInitial(() -> new HashMap<>());
private volatile String myCachedQName;
private volatile String myCachedNormalizedText;
@@ -181,28 +185,44 @@ public class PsiReferenceExpressionImpl extends ExpressionPsiElement implements
PsiReferenceExpressionImpl expression = (PsiReferenceExpressionImpl)ref;
CompositeElement treeParent = expression.getTreeParent();
IElementType parentType = treeParent == null ? null : treeParent.getElementType();
List<ResolveResult[]> qualifiers = resolveAllQualifiers(expression, containingFile);
JavaResolveResult[] result = expression.resolve(parentType, containingFile);
if (result.length == 0 && incompleteCode && parentType != JavaElementType.REFERENCE_EXPRESSION) {
result = expression.resolve(JavaElementType.REFERENCE_EXPRESSION, containingFile);
if (!incompleteCode) {
//optimization:
//for the expression foo().bar().baz(), first foo() is resolved during resolveAllQualifiers traversal
//then next qualifier is picked: foo().bar(); to resolve bar(), foo() should be resolved again
//if the global cache worked, then the result is already in ResolveCache
//if top level resolve was started in the context where caching is prohibited,
//foo() is already in the local cache ourQualifiersCache
ResolveResult[] result = ourQualifiersCache.get().get(ref);
if (result != null) {
return result;
}
}
JavaResolveUtil.substituteResults(expression, result);
boolean empty = ourQualifiersCache.get().isEmpty();
try {
resolveAllQualifiers(expression, containingFile);
JavaResolveResult[] result = expression.resolve(parentType, containingFile);
qualifiers.clear(); // hold qualifier target list until this moment to avoid psi elements inside to GC
if (result.length == 0 && incompleteCode && parentType != JavaElementType.REFERENCE_EXPRESSION) {
result = expression.resolve(JavaElementType.REFERENCE_EXPRESSION, containingFile);
}
return result;
JavaResolveUtil.substituteResults(expression, result);
return result;
}
finally {
//clear cache for the top level expression
if (empty) {
ourQualifiersCache.remove();
}
}
}
@NotNull
private static List<ResolveResult[]> resolveAllQualifiers(@NotNull PsiReferenceExpressionImpl expression, @NotNull PsiFile containingFile) {
private static void resolveAllQualifiers(@NotNull PsiReferenceExpressionImpl expression, @NotNull PsiFile containingFile) {
// to avoid SOE, resolve all qualifiers starting from the innermost
PsiElement qualifier = expression.getQualifier();
if (qualifier == null) return Collections.emptyList();
if (qualifier == null) return;
final List<ResolveResult[]> qualifiers = new SmartList<>();
final ResolveCache resolveCache = ResolveCache.getInstance(containingFile.getProject());
boolean physical = containingFile.isPhysical();
qualifier.accept(new JavaRecursiveElementWalkingVisitor() {
@@ -221,8 +241,9 @@ public class PsiReferenceExpressionImpl extends ExpressionPsiElement implements
@Override
protected void elementFinished(@NotNull PsiElement element) {
if (!(element instanceof PsiReferenceExpressionImpl)) return;
PsiReferenceExpressionImpl expression = (PsiReferenceExpressionImpl)element;
qualifiers.add(resolveCache.resolveWithCaching(expression, INSTANCE, false, false, containingFile));
PsiReferenceExpressionImpl chainedQualifier = (PsiReferenceExpressionImpl)element;
ourQualifiersCache.get()
.put(chainedQualifier, resolveCache.resolveWithCaching(chainedQualifier, INSTANCE, false, false, containingFile));
}
// walk only qualifiers, not their argument and other associated stuff
@@ -236,7 +257,6 @@ public class PsiReferenceExpressionImpl extends ExpressionPsiElement implements
@Override
public void visitClass(PsiClass aClass) { }
});
return qualifiers;
}
}

View File

@@ -0,0 +1,184 @@
import java.util.*;
class MyTest {
static Optional<BulkResult> map(BulkResultType bulkResultType) {
final LocalDateTime documentDateTime = Optional.ofNullable(bulkResultType)
.map(BulkResultType::getObservationBulkmailHeader)
.map(HeaderType::getDocumentDateTime)
.orElseThrow(() -> new InvalidMessageException(""));
final Optional<LocalDateTime> reportedDateTime = Optional.ofNullable(bulkResultType)
.map(BulkResultType::getObservationBulkmailHeader)
.map(bulkResultHeaderType::getResultReleaseTime)
.map(ResultReleaseTimeType::getReportedDateTime);
final Optional<LocalDateTime> lastModificationDateTime = Optional.ofNullable(bulkResultType)
.map(BulkResultType::getObservationBulkmailHeader)
.map(HeaderType::getLastModificationDateTime);
final Optional<String> statusCode =Optional.ofNullable(bulkResultType)
.map(BulkResultType::getObservationBulkmailHeader)
.map(bulkResultHeaderType::getStatus)
.flatMap(statusTypes -> statusTypes.stream().findFirst())
.map(StatusBasisType::getCode)
.map(CodeType::getValue);
return Optional.ofNullable(bulkResultType)
.flatMap(obm -> obm.getObservationBulkmailResult().stream().findFirst())
.map(obmResult -> BulkResult.builder()
.consignmentId(getConsignmentId(obmResult.getConsignmentID()))
.documentDateTime(documentDateTime)
.reportedDateTime(reportedDateTime)
.lastModificationDateTime(lastModificationDateTime)
.customerOrderId(Optional.ofNullable(obmResult.getCustomerOrderID()).map(IdentifierType::getValue))
.orderLineNumber(Optional.ofNullable(obmResult.getOrderLineNumber()).map(IdentifierType::getValue))
.startDateTime(Optional.ofNullable(obmResult.getResultTime()).map(TimePeriodABIEType::getStartDateTime))
.endDateTime(Optional.ofNullable(obmResult.getResultTime()).map(TimePeriodABIEType::getEndDateTime))
.statusCode(statusCode)
.build());
}
private static Object getConsignmentId(Object consignmentID) {
return null;
}
private static class Value { }
private static class CodeType {
String getValue() {
return null;
}
}
private static class IdentifierType {
public String getValue() {
return null;
}
}
private static class InvalidMessageException extends RuntimeException {
private InvalidMessageException(String s) {
}
}
private static class BulkResult {
public static Builder builder() {
return new Builder();
}
public static class Builder {
public Builder consignmentId(Object consignmentId) {
return null;
}
public Builder documentDateTime(LocalDateTime documentDateTime) {
return null;
}
public Builder reportedDateTime(Optional<LocalDateTime> reportedDateTime) {
return null;
}
public Builder lastModificationDateTime(Optional<LocalDateTime> lastModificationDateTime) {
return null;
}
public Builder customerOrderId(Optional<String> s) {
return null;
}
public Builder orderLineNumber(Optional<String> s) {
return null;
}
public Builder startDateTime(Optional<LocalDateTime> localDateTime) {
return null;
}
public Builder endDateTime(Optional<LocalDateTime> localDateTime) {
return null;
}
public Builder statusCode(Optional<String> statusCode) {
return null;
}
public BulkResult build() {
return null;
}
}
}
public static class bulkResultHeaderType {
public static ResultReleaseTimeType getResultReleaseTime(Object o) {
return new ResultReleaseTimeType();
}
public static List<StatusBasisType> getStatus(bulkResultHeaderType observationBulkmailHeaderType) {
return null;
}
}
public static class BulkResultResult {
public IdentifierType getCustomerOrderID() {
return null;
}
public IdentifierType getOrderLineNumber() {
return null;
}
public TimePeriodABIEType getResultTime() {
return null;
}
public Object getConsignmentID() {
return null;
}
}
public static class BulkResultType {
public static bulkResultHeaderType getObservationBulkmailHeader(BulkResultType bulkResultType) {
return new bulkResultHeaderType();
}
public Collection<BulkResultResult> getObservationBulkmailResult() {
return null;
}
}
public static class ResultReleaseTimeType {
public static LocalDateTime getReportedDateTime(Object o) {
return null;
}
}
public static class StatusBasisType {
public static CodeType getCode(Object o) {
return null;
}
}
public static class TimePeriodABIEType {
public LocalDateTime getStartDateTime() {return null;}
public LocalDateTime getEndDateTime() {return null;}
}
public static class HeaderType {
public static LocalDateTime getLastModificationDateTime(bulkResultHeaderType observationBulkmailHeaderType) {
return null;
}
public static LocalDateTime getDocumentDateTime(bulkResultHeaderType observationBulkmailHeaderType) {
return null;
}
}
public static class LocalDateTime {}
}

View File

@@ -16,19 +16,14 @@
package com.intellij.java.codeInsight.daemon.lambda;
import com.intellij.codeInsight.daemon.LightDaemonAnalyzerTestCase;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiManager;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl;
import one.util.streamex.IntStreamEx;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NonNls;
import java.util.List;
public class InferencePerformanceTest extends LightDaemonAnalyzerTestCase {
@NonNls static final String BASE_PATH = "/codeInsight/daemonCodeAnalyzer/lambda/performance";
@@ -72,6 +67,10 @@ public class InferencePerformanceTest extends LightDaemonAnalyzerTestCase {
assertEmpty(highlightErrors());
}
public void testLongQualifiersChainInsideLambda() {
PlatformTestUtil.startPerformanceTest("long qualifiers chain", 12000, this::doTest).usesAllCPUCores().assertTiming();
}
private void doTest() {
IdeaTestUtil.setTestVersion(JavaSdkVersion.JDK_1_8, getModule(), getTestRootDisposable());
doTest(BASE_PATH + "/" + getTestName(false) + ".java", false, false);