From 6216f7dca6a4e0de6b4b99c9e00c21d21f5cdc4d Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Tue, 19 Aug 2025 18:09:05 +0200 Subject: [PATCH] Java: improve ControlFlowUtil.isEffectivelyFinal() performance (IDEA-375485) this should improve ControlFlowPerformanceTest.testManyLocalVariables() GitOrigin-RevId: 95fbb002f546c8e1d29cc844cedddff230d144da --- .../psi/controlFlow/ControlFlowUtil.java | 131 +++++++----------- 1 file changed, 53 insertions(+), 78 deletions(-) diff --git a/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java b/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java index 43cef65cb12c..f27cfb694cf9 100644 --- a/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java +++ b/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java @@ -2403,64 +2403,46 @@ public final class ControlFlowUtil { return element instanceof PsiReturnStatement; } - private static class CopyOnWriteList { - private final List list; + private static class CopyOnWriteSet { + private final Set set; - public @NotNull CopyOnWriteList add(@NotNull VariableInfo value) { - CopyOnWriteList newList = new CopyOnWriteList(); - List list = getList(); - for (final VariableInfo variableInfo : list) { - if (!value.equals(variableInfo)) { - newList.list.add(variableInfo); - } - } - newList.list.add(value); + CopyOnWriteSet(@NotNull VariableInfo info) { + this(Collections.singletonList(info)); + } + + CopyOnWriteSet(@NotNull Collection infos) { + set = new HashSet<>(infos); + } + + public @NotNull CopyOnWriteSet add(@NotNull VariableInfo value) { + //if (set.contains(value)) return this; + CopyOnWriteSet newList = new CopyOnWriteSet(set); + newList.set.remove(value); + newList.set.add(value); return newList; } - public @NotNull CopyOnWriteList remove(@NotNull VariableInfo value) { - CopyOnWriteList newList = new CopyOnWriteList(); - List list = getList(); - for (final VariableInfo variableInfo : list) { - if (!value.equals(variableInfo)) { - newList.list.add(variableInfo); - } - } + public @NotNull CopyOnWriteSet remove(@NotNull VariableInfo value) { + if (!set.contains(value)) return this; + CopyOnWriteSet newList = new CopyOnWriteSet(set); + newList.set.remove(value); return newList; } - public @NotNull List getList() { - return list; + public @NotNull Set getSet() { + return set; } - CopyOnWriteList() { - this(Collections.emptyList()); - } - - CopyOnWriteList(VariableInfo @NotNull ... infos) { - this(Arrays.asList(infos)); - } - - CopyOnWriteList(@NotNull Collection infos) { - list = new SmartList<>(infos); - } - - public @NotNull CopyOnWriteList addAll(@NotNull CopyOnWriteList addList) { - CopyOnWriteList newList = new CopyOnWriteList(); - List list = getList(); - newList.list.addAll(list); - List toAdd = addList.getList(); - for (final VariableInfo variableInfo : toAdd) { - if (!newList.list.contains(variableInfo)) { - // no copy - newList.list.add(variableInfo); - } - } + public @NotNull CopyOnWriteSet addAll(@NotNull CopyOnWriteSet addList) { + Set toAdd = addList.getSet(); + if (set.containsAll(toAdd)) return this; + CopyOnWriteSet newList = new CopyOnWriteSet(set); + newList.set.addAll(toAdd); return newList; } - public static @NotNull CopyOnWriteList add(@Nullable CopyOnWriteList list, @NotNull VariableInfo value) { - return list == null ? new CopyOnWriteList(value) : list.add(value); + public static @NotNull CopyOnWriteSet add(@Nullable CopyOnWriteSet list, @NotNull VariableInfo value) { + return list == null ? new CopyOnWriteSet(value) : list.add(value); } } @@ -2494,9 +2476,9 @@ public final class ControlFlowUtil { } } - private static void merge(int offset, CopyOnWriteList source, CopyOnWriteList @NotNull [] target) { + private static void merge(int offset, CopyOnWriteSet source, CopyOnWriteSet @NotNull [] target) { if (source != null) { - CopyOnWriteList existing = target[offset]; + CopyOnWriteSet existing = target[offset]; target[offset] = existing == null ? source : existing.addAll(source); } } @@ -2516,29 +2498,29 @@ public final class ControlFlowUtil { } final ReadBeforeWriteClientVisitor visitor = new ReadBeforeWriteClientVisitor(flow, false); depthFirstSearch(flow, visitor); - return visitor.getResult(0); + return visitor.getResult(); } private static class ReadBeforeWriteClientVisitor extends InstructionClientVisitor> { // map of variable->PsiReferenceExpressions for all read before written variables for this point and below in control flow - private final CopyOnWriteList[] readVariables; + private final CopyOnWriteSet[] readVariables; private final ControlFlow myFlow; private final boolean localVariablesOnly; ReadBeforeWriteClientVisitor(@NotNull ControlFlow flow, boolean localVariablesOnly) { myFlow = flow; this.localVariablesOnly = localVariablesOnly; - readVariables = new CopyOnWriteList[myFlow.getSize() + 1]; + readVariables = new CopyOnWriteSet[myFlow.getSize() + 1]; } @Override public void visitReadVariableInstruction(ReadVariableInstruction instruction, int offset, int nextOffset) { - CopyOnWriteList readVars = readVariables[Math.min(nextOffset, myFlow.getSize())]; + CopyOnWriteSet readVars = readVariables[Math.min(nextOffset, myFlow.getSize())]; final PsiVariable variable = instruction.variable; if (!localVariablesOnly || !isImplicitlyInitialized(variable)) { final PsiReferenceExpression expression = getEnclosingReferenceExpression(myFlow.getElement(offset), variable); if (expression != null) { - readVars = CopyOnWriteList.add(readVars, new VariableInfo(variable, expression)); + readVars = CopyOnWriteSet.add(readVars, new VariableInfo(variable, expression)); } } merge(offset, readVars, readVariables); @@ -2546,7 +2528,7 @@ public final class ControlFlowUtil { @Override public void visitWriteVariableInstruction(WriteVariableInstruction instruction, int offset, int nextOffset) { - CopyOnWriteList readVars = readVariables[Math.min(nextOffset, myFlow.getSize())]; + CopyOnWriteSet readVars = readVariables[Math.min(nextOffset, myFlow.getSize())]; if (readVars == null) return; final PsiVariable variable = instruction.variable; @@ -2557,10 +2539,7 @@ public final class ControlFlowUtil { } private static boolean isImplicitlyInitialized(@NotNull PsiVariable variable) { - return isMethodParameter(variable) || variable instanceof PsiPatternVariable; - } - - private static boolean isMethodParameter(@NotNull PsiVariable variable) { + if (variable instanceof PsiPatternVariable) return true; if (variable instanceof PsiParameter) { final PsiParameter parameter = (PsiParameter)variable; return !(parameter.getDeclarationScope() instanceof PsiForeachStatement); @@ -2583,18 +2562,14 @@ public final class ControlFlowUtil { @Override public @NotNull List getResult() { - return getResult(0); - } - - public @NotNull List getResult(int startOffset) { - final CopyOnWriteList topReadVariables = readVariables[startOffset]; + final CopyOnWriteSet topReadVariables = readVariables[0]; if (topReadVariables == null) return Collections.emptyList(); final List result = new ArrayList<>(); - List list = topReadVariables.getList(); - for (final VariableInfo variableInfo : list) { - result.add((PsiReferenceExpression)variableInfo.expression); + for (VariableInfo info : topReadVariables.getSet()) { + result.add((PsiReferenceExpression)info.expression); } + Collections.sort(result, PsiUtil.BY_POSITION); return result; } } @@ -2663,24 +2638,24 @@ public final class ControlFlowUtil { private static class InitializedTwiceClientVisitor extends InstructionClientVisitor> { // map of variable->PsiReferenceExpressions for all read and not written variables for this point and below in control flow - private final CopyOnWriteList[] writtenVariables; - private final CopyOnWriteList[] writtenTwiceVariables; + private final CopyOnWriteSet[] writtenVariables; + private final CopyOnWriteSet[] writtenTwiceVariables; private final ControlFlow myFlow; private final int myStartOffset; InitializedTwiceClientVisitor(@NotNull ControlFlow flow, int startOffset) { myFlow = flow; myStartOffset = startOffset; - writtenVariables = new CopyOnWriteList[myFlow.getSize() + 1]; - writtenTwiceVariables = new CopyOnWriteList[myFlow.getSize() + 1]; + writtenVariables = new CopyOnWriteSet[myFlow.getSize() + 1]; + writtenTwiceVariables = new CopyOnWriteSet[myFlow.getSize() + 1]; } @Override public void visitInstruction(Instruction instruction, int offset, int nextOffset) { final int safeNextOffset = Math.min(nextOffset, myFlow.getSize()); - CopyOnWriteList writeVars = writtenVariables[safeNextOffset]; - CopyOnWriteList writeTwiceVars = writtenTwiceVariables[safeNextOffset]; + CopyOnWriteSet writeVars = writtenVariables[safeNextOffset]; + CopyOnWriteSet writeTwiceVars = writtenTwiceVariables[safeNextOffset]; if (instruction instanceof WriteVariableInstruction) { final PsiVariable variable = ((WriteVariableInstruction)instruction).variable; @@ -2688,10 +2663,10 @@ public final class ControlFlowUtil { if (latestWriteVarExpression == null) { final PsiElement expression = getExpression(myFlow.getElement(offset)); - writeVars = CopyOnWriteList.add(writeVars, new VariableInfo(variable, expression)); + writeVars = CopyOnWriteSet.add(writeVars, new VariableInfo(variable, expression)); } else { - writeTwiceVars = CopyOnWriteList.add(writeTwiceVars, new VariableInfo(variable, latestWriteVarExpression)); + writeTwiceVars = CopyOnWriteSet.add(writeTwiceVars, new VariableInfo(variable, latestWriteVarExpression)); } } merge(offset, writeVars, writtenVariables); @@ -2714,11 +2689,11 @@ public final class ControlFlowUtil { return null; } - private static @Nullable PsiElement getLatestWriteVarExpression(@Nullable CopyOnWriteList writeVars, @NotNull PsiVariable variable) { + private static @Nullable PsiElement getLatestWriteVarExpression(@Nullable CopyOnWriteSet writeVars, @NotNull PsiVariable variable) { if (writeVars == null) return null; PsiManager psiManager = variable.getManager(); - for (final VariableInfo variableInfo : writeVars.getList()) { + for (VariableInfo variableInfo : writeVars.getSet()) { if (psiManager.areElementsEquivalent(variableInfo.variable, variable)) { return variableInfo.expression; } @@ -2728,9 +2703,9 @@ public final class ControlFlowUtil { @Override public @NotNull Collection getResult() { - final CopyOnWriteList writtenTwiceVariable = writtenTwiceVariables[myStartOffset]; + final CopyOnWriteSet writtenTwiceVariable = writtenTwiceVariables[myStartOffset]; if (writtenTwiceVariable == null) return Collections.emptyList(); - return writtenTwiceVariable.getList(); + return writtenTwiceVariable.getSet(); } }