This commit is contained in:
Alexey Kudravtsev
2015-09-16 14:58:40 +03:00
parent 149627ffff
commit b99f18399b
4 changed files with 82 additions and 246 deletions
@@ -16,35 +16,104 @@
package com.intellij.openapi.vfs.newvfs.impl;
import com.intellij.openapi.vfs.newvfs.persistent.FSRecords;
import com.intellij.util.containers.LRUConcurrentIntObjectMap;
import com.intellij.util.IntSLRUCache;
import com.intellij.util.containers.IntObjectLinkedMap;
import com.intellij.util.io.PersistentStringEnumerator;
import com.intellij.util.text.ByteArrayCharSequence;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author peter
*/
public class FileNameCache {
private static final LRUConcurrentIntObjectMap<CharSequence> ourNameCache = new LRUConcurrentIntObjectMap<CharSequence>(65000);
private static final PersistentStringEnumerator ourNames = FSRecords.getNames();
@SuppressWarnings("unchecked") private static final IntSLRUCache<IntObjectLinkedMap.MapEntry<CharSequence>>[] ourNameCache = new IntSLRUCache[16];
static {
final int protectedSize = 40000 / ourNameCache.length;
final int probationalSize = 20000 / ourNameCache.length;
for(int i = 0; i < ourNameCache.length; ++i) {
ourNameCache[i] = new IntSLRUCache<IntObjectLinkedMap.MapEntry<CharSequence>>(protectedSize, probationalSize);
}
}
public static int storeName(@NotNull String name) {
final int idx = FSRecords.getNameId(name);
CharSequence rawName = ByteArrayCharSequence.convertToBytesIfAsciiString(name);
ourNameCache.put(idx, rawName);
cacheData(name, idx, calcStripeIdFromNameId(idx));
return idx;
}
private static final LRUConcurrentIntObjectMap.IntFunction<CharSequence> getNameById = new LRUConcurrentIntObjectMap.IntFunction<CharSequence>() {
@NotNull
@Override
public CharSequence apply(int id) {
String name = FSRecords.getNameByNameId(id);
return ByteArrayCharSequence.convertToBytesIfAsciiString(name);
@NotNull
private static IntObjectLinkedMap.MapEntry<CharSequence> cacheData(String name, int id, int stripe) {
if (name == null) {
ourNames.markCorrupted();
throw new RuntimeException("VFS name enumerator corrupted");
}
};
CharSequence rawName = ByteArrayCharSequence.convertToBytesIfAsciiString(name);
IntObjectLinkedMap.MapEntry<CharSequence> entry = new IntObjectLinkedMap.MapEntry<CharSequence>(id, rawName);
IntSLRUCache<IntObjectLinkedMap.MapEntry<CharSequence>> cache = ourNameCache[stripe];
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (cache) {
return cache.cacheEntry(entry);
}
}
private static int calcStripeIdFromNameId(int id) {
int h = id;
h -= h<<6;
h ^= h>>17;
h -= h<<9;
h ^= h<<4;
h -= h<<3;
h ^= h<<10;
h ^= h>>15;
return h % ourNameCache.length;
}
private static final boolean ourTrackStats = false;
private static final int ourLOneSize = 1024;
private static final IntObjectLinkedMap.MapEntry<CharSequence>[] ourArrayCache = new IntObjectLinkedMap.MapEntry[ourLOneSize];
private static final AtomicInteger ourQueries = new AtomicInteger();
private static final AtomicInteger ourMisses = new AtomicInteger();
@NotNull
public static CharSequence getVFileName(int nameId) {
assert nameId > 0 : nameId;
return ourNameCache.computeIfAbsent(nameId, getNameById);
assert nameId > 0;
if (ourTrackStats) {
int frequency = 10000000;
int queryCount = ourQueries.incrementAndGet();
if (queryCount >= frequency && ourQueries.compareAndSet(queryCount, 0)) {
double misses = ourMisses.getAndSet(0);
//noinspection UseOfSystemOutOrSystemErr
System.out.println("Misses: " + (misses / frequency));
ourQueries.set(0);
}
}
int l1 = nameId % ourLOneSize;
IntObjectLinkedMap.MapEntry<CharSequence> entry = ourArrayCache[l1];
if (entry != null && entry.key == nameId) {
return entry.value;
}
if (ourTrackStats) {
ourMisses.incrementAndGet();
}
final int stripe = calcStripeIdFromNameId(nameId);
IntSLRUCache<IntObjectLinkedMap.MapEntry<CharSequence>> cache = ourNameCache[stripe];
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (cache) {
entry = cache.getCachedEntry(nameId);
}
if (entry == null) {
entry = cacheData(FSRecords.getNameByNameId(nameId), nameId, stripe);
}
ourArrayCache[l1] = entry;
return entry.value;
}
}
@@ -1,70 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util.containers;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* A queue holding {@code int} values which has fixed capacity.
* When a value is pushed (via {@link #push} method) and the queue overflows (i.e. the size becomes greater than capacity),
* the LRU value (i.e. the value which was pushed a {@code capacity} pushes ago) is removed from the queue and returned as a method result.
* Thread safe.
*/
public class FixedConcurrentIntQueue {
private final AtomicInteger tail = new AtomicInteger();
final AtomicIntegerArray queue;
private final int capacity;
private final int tombValue;
/**
* @param capacity which restricts the queue size
* @param tombValue the value which is guaranteed to not be used in the queue.
*/
public FixedConcurrentIntQueue(int capacity, int tombValue) {
this.capacity = capacity;
this.tombValue = tombValue;
queue = new AtomicIntegerArray(capacity);
for (int i = 0; i < capacity; i++) {
queue.set(i, tombValue);
}
}
/**
* @param value to be pushed in the queue
* @return value which was evicted off the queue because of the overflow or {@link #tombValue} if no overflow happened
*/
public int push(int value) {
if (value == tombValue) {
throw new IllegalArgumentException("Must not use tomb value: "+value);
}
int index = getAndIncrement();
return queue.getAndSet(index, value);
}
private int getAndIncrement() {
int index;
int next;
do {
index = tail.get();
next = index + 1;
if (next >= capacity) {
next -= capacity;
}
} while (!tail.compareAndSet(index, next));
return index;
}
}
@@ -1,79 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util.containers;
import org.jetbrains.annotations.NotNull;
/**
* An {@code int->V} map with fixed {@code capacity}.
* When new key->value placed into the map and its size exceeded the capacity,
* the LRU oldKey->oldValue pair (i.e. the pair which was added into the map a {@code capacity} puts ago) is removed.
* Thread safe.
*/
public class LRUConcurrentIntObjectMap<V> {
private static final int TOMB_VALUE = -1;
/**
* the keys are stored in the usual ConcurrentIntObjectMap,
* while their LRU order is maintained in {@link #queue}. As soon as the key is evicted from the queue we remove the key->value pair from the map.
*/
private final ConcurrentIntObjectMap<V> myMap;
private final FixedConcurrentIntQueue queue;
public LRUConcurrentIntObjectMap(int capacity) {
queue = new FixedConcurrentIntQueue(capacity - capacity / 4 - 2 /* that's more or less the ConcurrentHashMap size which triggers resize*/, TOMB_VALUE);
myMap = ContainerUtil.createConcurrentIntObjectMap(capacity, 1, Runtime.getRuntime().availableProcessors());
}
public V get(int key) {
return myMap.get(key);
}
public interface IntFunction<R> {
@NotNull
R apply(int value);
}
@NotNull
public V computeIfAbsent(int key, @NotNull IntFunction<V> function) {
V v = myMap.get(key);
if (v != null) {
return v;
}
V newV = function.apply(key);
V prev = myMap.putIfAbsent(key, newV);
if (prev == null) {
addToQueue(key);
return newV;
}
return prev;
}
public V put(int key, V value) {
V prev = myMap.put(key, value);
if (prev == null) {
addToQueue(key);
}
return prev;
}
private void addToQueue(int key) {
int evicted = queue.push(key);
if (evicted != TOMB_VALUE) {
myMap.remove(evicted);
}
}
}
@@ -1,84 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util.containers;
import gnu.trove.TIntArrayList;
import junit.framework.TestCase;
import java.util.Set;
public class FixedConcurrentIntQueueTest extends TestCase {
public void testSimple() {
int tombValue = -1;
final FixedConcurrentIntQueue queue = new FixedConcurrentIntQueue(4, tombValue);
assertEquals(tombValue, queue.push(2));
assertEquals(tombValue, queue.push(3));
assertEquals(tombValue, queue.push(4));
assertEquals(tombValue, queue.push(5));
assertEquals(2, queue.push(6));
assertEquals(3, queue.push(7));
assertEquals(4, queue.push(8));
assertEquals(5, queue.push(9));
assertEquals(6, queue.push(0));
}
public void testIntegerOverflow() {
int tombValue = -1;
final FixedConcurrentIntQueue queue = new FixedConcurrentIntQueue(4, tombValue);
for (int i=10; i!=9; i++) {
queue.push(2); // must not AIOOBE
}
}
public void testStress() throws InterruptedException {
for (int k=0; k<100; k++) {
final int N = 1 << 20;
final FixedConcurrentIntQueue queue = new FixedConcurrentIntQueue(N, -1);
final Thread[] threads = new Thread[8];
final Set<Integer> result = new ConcurrentHashSet<Integer>();
for (int i = 0; i < threads.length; i++) {
final int finalI = i;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
TIntArrayList evicted = new TIntArrayList();
for (int i = N / threads.length * finalI; i < N / threads.length * finalI + N / threads.length; i++) {
int f = queue.push(i);
evicted.add(f);
}
for (int f : evicted.toNativeArray()) {
result.add(f);
}
}
},i + "");
threads[i] = thread;
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
int size = result.size();
assertEquals(1, size);
result.clear();
for (int i=0; i<N;i++) {
result.add(queue.queue.get(i));
}
assertEquals(N, result.size());
}
}
}