optimization: use a primitive-aware map to avoid frequent TextRange allocations

GitOrigin-RevId: ac3378ca9d04b265b1a3ec4aa2fd710615e0219a
This commit is contained in:
Alexey Kudravtsev
2023-12-19 14:42:16 +00:00
committed by intellij-monorepo-bot
parent 9b05a93fbc
commit 8d960a4c66
2 changed files with 48 additions and 17 deletions
@@ -2,28 +2,36 @@
package com.intellij.codeInsight.daemon.impl;
import com.intellij.openapi.editor.ex.RangeHighlighterEx;
import com.intellij.openapi.editor.impl.RangeMarkerImpl;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.ProperTextRange;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.TextRangeScalarUtil;
import com.intellij.openapi.util.UserDataHolderEx;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.containers.ContainerUtil;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
// cache for highlighters not needed anymore.
// You call recycleHighlighter() to put unused highlighter into the cache
// and then call pickupHighlighterFromGarbageBin() (if there is a sudden need for fresh highlighter with specified offsets) to remove it from the cache to re-initialize and use.
/**
* Cache for highlighters scheduled for removal.
* You call {@link #recycleHighlighter} to put unused highlighter into the cache
* and then call {@link #pickupHighlighterFromGarbageBin} (if there is a sudden need for fresh highlighter with specified offsets) to remove it from the cache to re-initialize and re-use.
* In the end, call {@link UpdateHighlightersUtil#incinerateObsoleteHighlighters} to finally remove highlighters left in the cache that nobody picked up and reused.
*/
final class HighlightersRecycler {
private final MultiMap<TextRange, RangeHighlighterEx> incinerator = new MultiMap<>();
private final Long2ObjectMap<List<RangeHighlighterEx>> incinerator = new Long2ObjectOpenHashMap<>();
private static final Key<Boolean> BEING_RECYCLED_KEY = Key.create("RECYCLED_KEY"); // set when the highlighter is just recycled, but not yet transferred to EDT to change its attributes. used to prevent double recycling the same RH
// return true if RH is successfully recycled, false if race condition intervened
boolean recycleHighlighter(@NotNull RangeHighlighterEx highlighter) {
if (highlighter.isValid() && ((UserDataHolderEx)highlighter).replace(BEING_RECYCLED_KEY, null, Boolean.TRUE)) {
incinerator.putValue(ProperTextRange.create(highlighter), highlighter);
long range = ((RangeMarkerImpl)highlighter).getScalarRange();
incinerator.computeIfAbsent(range, __ -> new ArrayList<>()).add(highlighter);
return true;
}
return false;
@@ -31,13 +39,19 @@ final class HighlightersRecycler {
@Nullable // null means no highlighter found in the cache
RangeHighlighterEx pickupHighlighterFromGarbageBin(int startOffset, int endOffset, int layer) {
TextRange range = new TextRange(startOffset, endOffset);
Collection<RangeHighlighterEx> collection = incinerator.get(range);
for (RangeHighlighterEx highlighter : collection) {
if (highlighter.isValid() && highlighter.getLayer() == layer) {
incinerator.remove(range, highlighter);
highlighter.putUserData(BEING_RECYCLED_KEY, null);
return highlighter;
long range = TextRangeScalarUtil.toScalarRange(startOffset, endOffset);
List<RangeHighlighterEx> collection = incinerator.get(range);
if (collection != null) {
for (int i = 0; i < collection.size(); i++) {
RangeHighlighterEx highlighter = collection.get(i);
if (highlighter.isValid() && highlighter.getLayer() == layer) {
collection.remove(i);
if (collection.isEmpty()) {
incinerator.remove(range);
}
highlighter.putUserData(BEING_RECYCLED_KEY, null);
return highlighter;
}
}
}
return null;
@@ -45,7 +59,7 @@ final class HighlightersRecycler {
@NotNull
Collection<? extends RangeHighlighter> forAllInGarbageBin() {
return incinerator.values();
return ContainerUtil.flatten(incinerator.values());
}
// mark all remaining highlighters as not "recycled", to avoid double creation
@@ -54,7 +68,8 @@ final class HighlightersRecycler {
((UserDataHolderEx)highlighter).replace(BEING_RECYCLED_KEY, Boolean.TRUE, null);
}
}
static boolean isBeingRecycled(RangeHighlighter highlighter) {
static boolean isBeingRecycled(@NotNull RangeHighlighter highlighter) {
return highlighter.getUserData(BEING_RECYCLED_KEY) != null;
}
}
@@ -19,6 +19,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.DocumentUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.diff.FilesTooBigForDiffException;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -420,6 +421,21 @@ public class RangeMarkerImpl extends UserDataHolderBase implements RangeMarkerEx
return node.intervalEnd();
}
/**
* @return this marker text range in the scalar form
*/
@ApiStatus.Internal
public long getScalarRange() {
RangeMarkerTree.RMNode<?> node = myNode;
if (node == null) {
return -1;
}
long range = node.toScalarRange();
int delta = node.computeDeltaUpToRoot();
return TextRangeScalarUtil.shift(range, delta, delta);
}
// return intrinsic range belonging to that node (without delta-up-to-the-root correction)
long toScalarRange() {
RangeMarkerTree.RMNode<?> node = myNode;
if (node == null) {