[performance] different interner for path segments

+ implement simplest MRU-like interner, instead of Caffeine -- Caffeine seems to be an overkill for such a simple task.
+ Use `-Dcompiler.InternedPath.USE_CAFFEINE_INTERNER=true` to return Caffeine-based interner

GitOrigin-RevId: 4b95e28dc2c4303d9078be445ee8f94e593ac176
This commit is contained in:
Ruslan Cheremin
2025-07-14 19:54:03 +00:00
committed by intellij-monorepo-bot
parent 2264207f08
commit 7f76ca88d7
@@ -3,6 +3,7 @@ package com.intellij.compiler.server;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import it.unimi.dsi.fastutil.HashCommon;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -10,12 +11,21 @@ import java.io.File;
import java.util.*;
import java.util.function.Predicate;
import static com.intellij.util.SystemProperties.getBooleanProperty;
/**
* Specialized data structure for compact in-memory storage of big amounts of path-like strings
*/
public abstract sealed class InternedPath permits InternedPath.WinInternedPath, InternedPath.UNCWinInternedPath, InternedPath.XInternedPath {
private static final String PATH_SEPARATORS = '/' == File.separatorChar? File.separator : "/" + File.separator;
private static final LoadingCache<String, String> ourNameCache = Caffeine.newBuilder().maximumSize(2048).build(key -> key);
private static final String PATH_SEPARATORS = ('/' == File.separatorChar) ?
File.separator :
"/" + File.separator;
private static final boolean USE_CAFFEINE_INTERNER = getBooleanProperty("compiler.InternedPath.USE_CAFFEINE_INTERNER", false);
private static final PathSegmentsInterner ourNameCache = USE_CAFFEINE_INTERNER ?
new CaffeineBasedInterner() :
new SimplestMRUInterner();
protected final String[] myPath;
@@ -26,7 +36,7 @@ public abstract sealed class InternedPath permits InternedPath.WinInternedPath,
List<String> list = new ArrayList<>();
StringTokenizer tokenizer = new StringTokenizer(path, PATH_SEPARATORS, false);
while (tokenizer.hasMoreTokens()) {
list.add(ourNameCache.get(tokenizer.nextToken()));
list.add(ourNameCache.intern(tokenizer.nextToken()));
}
myPath = list.toArray(String[]::new);
}
@@ -36,13 +46,13 @@ public abstract sealed class InternedPath permits InternedPath.WinInternedPath,
@NotNull
public String getName() {
return myPath.length > 0? myPath[myPath.length - 1] : "";
return myPath.length > 0 ? myPath[myPath.length - 1] : "";
}
public boolean contains(Predicate<String> pathElementMatcher) {
return find(pathElementMatcher) != null;
}
@Nullable
public String find(Predicate<String> pathElementMatcher) {
for (String elem : myPath) {
@@ -88,7 +98,9 @@ public abstract sealed class InternedPath permits InternedPath.WinInternedPath,
}
public static InternedPath create(String path) {
return path.startsWith("//") || path.startsWith("\\\\")? new UNCWinInternedPath(path.substring(2)) : path.startsWith("/")? new XInternedPath(path) : new WinInternedPath(path);
return path.startsWith("//") || path.startsWith("\\\\")
? new UNCWinInternedPath(path.substring(2))
: path.startsWith("/") ? new XInternedPath(path) : new WinInternedPath(path);
}
static final class WinInternedPath extends InternedPath {
@@ -105,13 +117,13 @@ public abstract sealed class InternedPath permits InternedPath.WinInternedPath,
if (myPath.length == 1) {
String name = myPath[0];
// handle the case of a Windows volume name
return name.length() == 2 && name.endsWith(":")? name + "/" : name;
return name.length() == 2 && name.endsWith(":") ? name + '/' : name;
}
final StringBuilder buf = new StringBuilder();
for (CharSequence element : myPath) {
if (!buf.isEmpty()) {
buf.append("/");
buf.append('/');
}
buf.append(element);
}
@@ -131,9 +143,9 @@ public abstract sealed class InternedPath permits InternedPath.WinInternedPath,
return "//";
}
final StringBuilder buf = new StringBuilder();
buf.append("/");
buf.append('/');
for (CharSequence element : myPath) {
buf.append("/").append(element);
buf.append('/').append(element);
}
return buf.toString();
}
@@ -157,4 +169,50 @@ public abstract sealed class InternedPath permits InternedPath.WinInternedPath,
return "/";
}
}
private interface PathSegmentsInterner {
String intern(@NotNull String segment);
void invalidateAll();
}
/** String interner: very simple but very fast and suitable for concurrent environment */
private static class SimplestMRUInterner implements PathSegmentsInterner {
private static final int CACHE_SIZE = (1 << 12); // =4k
private final String[] cache = new String[CACHE_SIZE];
@Override
public String intern(@NotNull String segment) {
int index = HashCommon.mix(segment.hashCode()) & (CACHE_SIZE - 1);
String candidate = cache[index];
if (segment.equals(candidate)) {
return candidate;
}
cache[index] = segment;
return segment;
}
@Override
public void invalidateAll(){
Arrays.fill(cache, null);
}
}
private static class CaffeineBasedInterner implements PathSegmentsInterner {
private static final LoadingCache<@NotNull String, String> cache = Caffeine.newBuilder().maximumSize(2048).build(key -> key);
@Override
public String intern(@NotNull String segment) {
return cache.get(segment);
}
@Override
public void invalidateAll() {
cache.invalidateAll();
}
}
}