diff --git a/java/java-tests/testSrc/com/intellij/psi/impl/smartPointers/SmartPsiElementPointersTest.java b/java/java-tests/testSrc/com/intellij/psi/impl/smartPointers/SmartPsiElementPointersTest.java
index 38e03ea9fc5e..1425b9b3ab76 100644
--- a/java/java-tests/testSrc/com/intellij/psi/impl/smartPointers/SmartPsiElementPointersTest.java
+++ b/java/java-tests/testSrc/com/intellij/psi/impl/smartPointers/SmartPsiElementPointersTest.java
@@ -38,6 +38,7 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
+import com.intellij.psi.impl.source.PostprocessReformattingAspect;
import com.intellij.psi.impl.source.PsiFileImpl;
import com.intellij.psi.impl.source.tree.FileElement;
import com.intellij.psi.search.GlobalSearchScope;
@@ -46,17 +47,19 @@ import com.intellij.psi.stubs.StubTree;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilBase;
+import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.testFramework.*;
import com.intellij.util.FileContentUtil;
+import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import java.io.IOException;
import java.lang.ref.SoftReference;
-import java.util.Collections;
-import java.util.Set;
+import java.util.*;
+import java.util.stream.Collectors;
@PlatformTestCase.WrapInCommand
@SkipSlowTestLocally
@@ -849,6 +852,27 @@ public class SmartPsiElementPointersTest extends CodeInsightTestCase {
assertNotNull(pointer.getElement());
}
+ public void testManyPsiChangesWithManySmartPointersPerformance() {
+ String eachTag = "\n" + StringUtil.repeat(" \n", 9) + "\n";
+ XmlFile file = (XmlFile)configureByText(XmlFileType.INSTANCE, "\n" + StringUtil.repeat(eachTag, 500) + "");
+ List tags = ContainerUtil.newArrayList(PsiTreeUtil.findChildrenOfType(file.getDocument(), XmlTag.class));
+ List pointers = tags.stream().map(this::createPointer).collect(Collectors.toList());
+ Random random = new Random();
+ PlatformTestUtil.startPerformanceTest("smart pointer range update after PSI change", 21000, () -> {
+ for (int i = 0; i < tags.size(); i++) {
+ XmlTag tag = tags.get(i);
+ SmartPsiElementPointer pointer = pointers.get(i);
+ assertEquals(tag.getName().length(), TextRange.create(pointer.getRange()).getLength());
+ assertEquals(tag.getName().length(), TextRange.create(pointer.getPsiRange()).getLength());
+
+ tag.setName("bar" + random.nextInt(20));
+ assertEquals(tag.getName().length(), TextRange.create(pointer.getRange()).getLength());
+ assertEquals(tag.getName().length(), TextRange.create(pointer.getPsiRange()).getLength());
+ }
+ PostprocessReformattingAspect.getInstance(myProject).doPostponedFormatting();
+ }).cpuBound().assertTiming();
+ }
+
@NotNull
private SmartPointerEx createPointer(T element) {
return (SmartPointerEx)getPointerManager().createSmartPsiElementPointer(element);
diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/AnchorElementInfo.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/AnchorElementInfo.java
index c45060bff642..8b3df52d329b 100644
--- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/AnchorElementInfo.java
+++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/AnchorElementInfo.java
@@ -45,7 +45,7 @@ class AnchorElementInfo extends SelfElementInfo {
@NotNull PsiFileWithStubSupport containingFile,
int stubId,
@NotNull IStubElementType stubElementType) {
- super(containingFile.getProject(), new ProperTextRange(0, 0), anchor.getClass(), containingFile, containingFile.getLanguage(), false);
+ super(containingFile.getProject(), null, anchor.getClass(), containingFile, containingFile.getLanguage(), false);
myStubElementTypeAndId = pack(stubId, stubElementType);
assert !(anchor instanceof PsiFile) : "FileElementInfo must be used for file: "+anchor;
}
@@ -130,7 +130,7 @@ class AnchorElementInfo extends SelfElementInfo {
if (anchor == null) anchor = element;
myType = anchor.getClass();
setRange(anchor.getTextRange());
- myMarkerCache.rangeChanged(markerCacheKey());
+ myMarkerCache.rangeChanged();
myStubElementTypeAndId = pack(-1, null);
}
}
diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/MarkerCache.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/MarkerCache.java
index 02678e34f0f4..dd2c0028bb71 100644
--- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/MarkerCache.java
+++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/MarkerCache.java
@@ -21,70 +21,98 @@ import com.intellij.openapi.editor.impl.ManualRangeMarker;
import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
import com.intellij.openapi.editor.impl.event.RetargetRangeMarkers;
import com.intellij.openapi.util.ProperTextRange;
-import com.intellij.openapi.util.Trinity;
import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.util.NullableFunction;
+import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
-import gnu.trove.TLongObjectHashMap;
-import gnu.trove.TObjectFunction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.lang.ref.Reference;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
/**
* @author peter
*/
class MarkerCache {
+ private static final Comparator BY_RANGE_KEY = new Comparator() {
+ @Override
+ public int compare(SelfElementInfo info1, SelfElementInfo info2) {
+ int o1 = info1.getPsiStartOffset();
+ int o2 = info2.getPsiStartOffset();
+ if (o1 != o2) return o1 > o2 ? 1 : -1;
+
+ o1 = info1.getPsiEndOffset();
+ o2 = info2.getPsiEndOffset();
+ if (o1 != o2) return o1 > o2 ? 1 : -1;
+
+ return (info1.isForInjected() ? 1 : 0) - (info2.isForInjected() ? 1 : 0);
+ }
+ };
private final SmartPointerManagerImpl.FilePointersList myPointers;
private final VirtualFile myVirtualFile;
- private volatile Trinity, FrozenDocument> myUpdatedRanges;
+ private volatile UpdatedRanges myUpdatedRanges;
MarkerCache(SmartPointerManagerImpl.FilePointersList pointers, VirtualFile virtualFile) {
myPointers = pointers;
myVirtualFile = virtualFile;
}
- private TLongObjectHashMap getUpdatedMarkers(@NotNull FrozenDocument frozen, @NotNull List events) {
+ private UpdatedRanges getUpdatedMarkers(@NotNull FrozenDocument frozen, @NotNull List events) {
int eventCount = events.size();
assert eventCount > 0;
- Trinity, FrozenDocument> cache = myUpdatedRanges;
- if (cache != null && cache.first.intValue() == eventCount) return cache.second;
+ UpdatedRanges cache = myUpdatedRanges;
+ if (cache != null && cache.myEventCount == eventCount) return cache;
//noinspection SynchronizeOnThis
synchronized (this) {
cache = myUpdatedRanges;
- if (cache != null && cache.first.intValue() == eventCount) return cache.second;
+ if (cache != null && cache.myEventCount == eventCount) return cache;
- TLongObjectHashMap answer;
- if (cache != null && cache.first < eventCount) {
+ UpdatedRanges answer;
+ if (cache != null && cache.myEventCount < eventCount) {
// apply only the new events
- answer = cache.second.clone();
- frozen = applyEvents(cache.third, events.subList(cache.first, eventCount), answer);
+ answer = applyEvents(events.subList(cache.myEventCount, eventCount), cache);
}
else {
- List infos = getInfos();
- answer = new TLongObjectHashMap(infos.size());
- for (SelfElementInfo info : infos) {
- ProperTextRange range = info.getPsiRange();
- long key = info.markerCacheKey();
- if (range != null && key != 0) {
- boolean forInjected = info.isForInjected();
- answer.put(key, new ManualRangeMarker(range, forInjected, forInjected, !forInjected, null));
- }
- }
- frozen = applyEvents(frozen, events, answer);
+ List infos = getSortedInfos();
+ ManualRangeMarker[] markers = createMarkers(infos);
+ answer = applyEvents(events, new UpdatedRanges(0, frozen, infos, markers));
}
- myUpdatedRanges = Trinity.create(eventCount, answer, frozen);
+ myUpdatedRanges = answer;
return answer;
}
}
- private static FrozenDocument applyEvents(@NotNull FrozenDocument frozen,
- @NotNull List events,
- final TLongObjectHashMap map) {
+ @NotNull
+ private static ManualRangeMarker[] createMarkers(List infos) {
+ ManualRangeMarker[] markers = new ManualRangeMarker[infos.size()];
+ int i = 0;
+ while (i < markers.length) {
+ SelfElementInfo info = infos.get(i);
+ boolean forInjected = info.isForInjected();
+ ProperTextRange range = ObjectUtils.assertNotNull(info.getPsiRange());
+ markers[i] = new ManualRangeMarker(range, forInjected, forInjected, !forInjected, null);
+
+ i++;
+ while (i < markers.length && rangeEquals(infos.get(i), range, forInjected)) {
+ markers[i] = markers[i - 1];
+ i++;
+ }
+ }
+ return markers;
+ }
+
+ private static boolean rangeEquals(SelfElementInfo info, ProperTextRange range, boolean injected) {
+ return range.getStartOffset() == info.getPsiStartOffset() && range.getEndOffset() == info.getPsiEndOffset() && injected == info.isForInjected();
+ }
+
+ private static UpdatedRanges applyEvents(@NotNull List events, final UpdatedRanges struct) {
+ FrozenDocument frozen = struct.myResultDocument;
+ ManualRangeMarker[] resultMarkers = struct.myMarkers.clone();
for (DocumentEvent event : events) {
final FrozenDocument before = frozen;
final DocumentEvent corrected;
@@ -99,53 +127,83 @@ class MarkerCache {
((DocumentEventImpl) event).getInitialStartOffset(), ((DocumentEventImpl) event).getInitialOldLength());
}
- map.transformValues(new TObjectFunction() {
- @Override
- public ManualRangeMarker execute(ManualRangeMarker currentRange) {
- return currentRange == null ? null : currentRange.getUpdatedRange(corrected, before);
+ int i = 0;
+ while (i < resultMarkers.length) {
+ ManualRangeMarker currentRange = resultMarkers[i];
+
+ int sameMarkersEnd = i + 1;
+ while (sameMarkersEnd < resultMarkers.length && resultMarkers[sameMarkersEnd] == currentRange) {
+ sameMarkersEnd++;
}
- });
+
+ ManualRangeMarker updatedRange = currentRange == null ? null : currentRange.getUpdatedRange(corrected, before);
+ while (i < sameMarkersEnd) {
+ resultMarkers[i] = updatedRange;
+ i++;
+ }
+ }
}
- return frozen;
+ return new UpdatedRanges(struct.myEventCount + events.size(), frozen, struct.mySortedInfos, resultMarkers);
}
synchronized void updateMarkers(@NotNull FrozenDocument frozen, @NotNull List events) {
- TLongObjectHashMap updated = getUpdatedMarkers(frozen, events);
+ UpdatedRanges updated = getUpdatedMarkers(frozen, events);
- for (SelfElementInfo info : getInfos()) {
- long key = info.markerCacheKey();
- if (key != 0) {
- ManualRangeMarker newRangeMarker = updated.get(key);
- info.setRange(newRangeMarker == null ? null : newRangeMarker.getRange());
- }
+ for (int i = 0; i < updated.myMarkers.length; i++) {
+ ManualRangeMarker newRangeMarker = updated.myMarkers[i];
+ updated.mySortedInfos.get(i).setRange(newRangeMarker == null ? null : newRangeMarker.getRange());
}
myUpdatedRanges = null;
}
@NotNull
- private List getInfos() {
- return ContainerUtil.findAll(ContainerUtil.map(myPointers.getAlivePointers(), new NullableFunction() {
- @Override
- public SmartPointerElementInfo fun(SmartPsiElementPointerImpl pointer) {
- return pointer.getElementInfo();
+ private List getSortedInfos() {
+ List infos = ContainerUtil.newArrayListWithCapacity(myPointers.getSize());
+ for (Reference reference : myPointers.getReferences()) {
+ if (reference != null) {
+ SmartPointerEx pointer = reference.get();
+ if (pointer != null) {
+ SmartPointerElementInfo info = ((SmartPsiElementPointerImpl)pointer).getElementInfo();
+ if (info instanceof SelfElementInfo && ((SelfElementInfo)info).hasRange()) {
+ infos.add((SelfElementInfo)info);
+ }
}
- }), SelfElementInfo.class);
+ }
+ }
+ Collections.sort(infos, BY_RANGE_KEY);
+ return infos;
}
@Nullable
- ProperTextRange getUpdatedRange(long rangeKey, @NotNull FrozenDocument frozen, @NotNull List events) {
- ManualRangeMarker updated = getUpdatedMarkers(frozen, events).get(rangeKey);
+ ProperTextRange getUpdatedRange(@NotNull SelfElementInfo info, @NotNull FrozenDocument frozen, @NotNull List events) {
+ UpdatedRanges struct = getUpdatedMarkers(frozen, events);
+ int i = Collections.binarySearch(struct.mySortedInfos, info, BY_RANGE_KEY);
+ ManualRangeMarker updated = i >= 0 ? struct.myMarkers[i] : null;
return updated == null ? null : updated.getRange();
}
- synchronized void rangeChanged(long rangeKey) {
- if (myUpdatedRanges != null && !myUpdatedRanges.second.contains(rangeKey)) {
- myUpdatedRanges = null;
- }
+ void rangeChanged() {
+ myUpdatedRanges = null;
}
VirtualFile getVirtualFile() {
return myVirtualFile;
}
+
+ private static class UpdatedRanges {
+ private final int myEventCount;
+ private final FrozenDocument myResultDocument;
+ private final List mySortedInfos;
+ private final ManualRangeMarker[] myMarkers;
+
+ public UpdatedRanges(int eventCount,
+ FrozenDocument resultDocument,
+ List sortedInfos, ManualRangeMarker[] markers) {
+ myEventCount = eventCount;
+ myResultDocument = resultDocument;
+ mySortedInfos = sortedInfos;
+ myMarkers = markers;
+ }
+ }
}
diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SelfElementInfo.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SelfElementInfo.java
index 9e1a761383a6..63fb55f566de 100644
--- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SelfElementInfo.java
+++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SelfElementInfo.java
@@ -42,12 +42,11 @@ public class SelfElementInfo extends SmartPointerElementInfo {
protected final Language myLanguage;
protected final MarkerCache myMarkerCache;
private final boolean myForInjected;
- private boolean myHasRange;
private int myStartOffset;
private int myEndOffset;
SelfElementInfo(@NotNull Project project,
- @NotNull ProperTextRange range,
+ @Nullable ProperTextRange range,
@NotNull Class anchorClass,
@NotNull PsiFile containingFile,
@NotNull Language language,
@@ -60,35 +59,30 @@ public class SelfElementInfo extends SmartPointerElementInfo {
myManager = (SmartPointerManagerImpl)SmartPointerManager.getInstance(project);
myMarkerCache = myManager.getMarkerCache(containingFile.getViewProvider().getVirtualFile());
setRange(range);
- myMarkerCache.rangeChanged(markerCacheKey());
+ myMarkerCache.rangeChanged();
}
void setRange(@Nullable Segment range) {
if (range != null) {
- myHasRange = true;
myStartOffset = range.getStartOffset();
myEndOffset = range.getEndOffset();
} else {
- myHasRange = false;
+ myStartOffset = -1;
+ myEndOffset = -1;
}
}
- long markerCacheKey() {
- if (!myHasRange) return 0;
-
- long start = myStartOffset;
- assert start >= 0;
- assert start < Integer.MAX_VALUE;
-
- long packed = (start + 1) | ((long)myEndOffset << 32);
- assert packed > 0;
- assert packed != Long.MIN_VALUE;
-
- long result = myForInjected ? -packed : packed;
- assert result != 0;
- return result;
+ boolean hasRange() {
+ return myStartOffset >= 0;
}
+ int getPsiStartOffset() {
+ return myStartOffset;
+ }
+
+ int getPsiEndOffset() {
+ return myEndOffset;
+ }
boolean isForInjected() {
return myForInjected;
@@ -118,7 +112,7 @@ public class SelfElementInfo extends SmartPointerElementInfo {
@Nullable
private ProperTextRange calcPsiRange() {
- return myHasRange ? ProperTextRange.create(myStartOffset, myEndOffset) : null;
+ return hasRange() ? ProperTextRange.create(myStartOffset, myEndOffset) : null;
}
@Override
@@ -179,7 +173,7 @@ public class SelfElementInfo extends SmartPointerElementInfo {
@Override
public void cleanup() {
- myHasRange = false;
+ setRange(null);
}
@Nullable
@@ -270,13 +264,13 @@ public class SelfElementInfo extends SmartPointerElementInfo {
@Override
@Nullable
public Segment getRange() {
- if (myHasRange) {
+ if (hasRange()) {
Document document = getDocumentToSynchronize();
if (document != null) {
PsiDocumentManagerBase documentManager = myManager.getPsiDocumentManager();
List events = documentManager.getEventsSinceCommit(document);
if (!events.isEmpty()) {
- return myMarkerCache.getUpdatedRange(markerCacheKey(), (FrozenDocument)documentManager.getLastCommittedDocument(document), events);
+ return myMarkerCache.getUpdatedRange(this, (FrozenDocument)documentManager.getLastCommittedDocument(document), events);
}
}
}
diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java
index 178117ecec57..d7ad67908b04 100644
--- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java
+++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java
@@ -344,6 +344,14 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
return size == 0;
}
+ Reference[] getReferences() {
+ return references;
+ }
+
+ int getSize() {
+ return size;
+ }
+
@NotNull
List getAlivePointers() {
return ContainerUtil.mapNotNull(references, new Function() {