PY-3999 PY-8207 PY-7309

PyClass ancestors are now cached in smart way using type eval context as key
This commit is contained in:
Ilya.Kazakevich
2015-08-07 21:31:35 +03:00
parent 032399b940
commit f7bfe85f23
3 changed files with 138 additions and 49 deletions
@@ -0,0 +1,89 @@
/*
* 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.jetbrains.python.psi.types;
import com.intellij.psi.util.*;
import com.intellij.util.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
/**
* Engine to cache something in map, where {@link TypeEvalContext} is used as key.
* This cache is weak-based (no memory leaks), thread safe and purges on any PSI change.
*
* @author Ilya.Kazakevich
*/
public final class TypeEvalContextBasedCache<T> {
/**
* Lock to sync
*/
@NotNull
private final Object myLock = new Object();
@NotNull
private final CachedValue<Map<TypeEvalConstraints, T>> myCachedMapStorage;
@NotNull
private final Function<TypeEvalContext, T> myProvider;
/**
* @param manager Cache manager to be used to store cache
* @param valueProvider engine to create value based on context.
*/
public TypeEvalContextBasedCache(@NotNull final CachedValuesManager manager,
@NotNull final Function<TypeEvalContext, T> valueProvider) {
myCachedMapStorage = manager.createCachedValue(new MapCreator<T>(), false);
myProvider = valueProvider;
}
/**
* Returns value (executes provider to obtain new if no any and stores it in cache)
* @param context to be used as key
* @return value
*/
@NotNull
public T getValue(@NotNull final TypeEvalContext context) {
// Map is not thread safe, and "getValue" is not atomic. I do not want several maps to be created.
synchronized (myLock) {
final Map<TypeEvalConstraints, T> map = myCachedMapStorage.getValue();
T value = map.get(context.getConstraints());
if (value != null) {
return value;
}
// This is the same value, semantically: value for context-key
//noinspection ReuseOfLocalVariable
value = myProvider.fun(context);
map.put(context.getConstraints(), value);
return value;
}
}
/**
* Provider that creates map to store cache. Map depends on PSI modification
*/
private static final class MapCreator<T> implements CachedValueProvider<Map<TypeEvalConstraints, T>> {
@Nullable
@Override
public Result<Map<TypeEvalConstraints, T>> compute() {
// This method is called if cache is empty. Create new map for it.
final HashMap<TypeEvalConstraints, T> map = new HashMap<TypeEvalConstraints, T>();
return new Result<Map<TypeEvalConstraints, T>>(map, PsiModificationTracker.MODIFICATION_COUNT);
}
}
}
@@ -15,15 +15,9 @@
*/
package com.jetbrains.python.psi.types;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.util.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
/**
* Caches context by their constraints (to prevent context cache loss). Flushes cache every PSI change or low memory conditions.
@@ -34,53 +28,27 @@ import java.util.Map;
*/
final class TypeEvalContextCacheImpl implements TypeEvalContextCache {
/**
* Producer to create map to store cache
*/
@NotNull
private static final MapCreator MAP_CREATOR = new MapCreator();
/**
* Lock to sync
*/
private static final Function<TypeEvalContext, TypeEvalContext> VALUE_PROVIDER = new MyValueProvider();
@NotNull
private final Object myLock = new Object();
@NotNull
private final CachedValue<Map<TypeEvalConstraints, TypeEvalContext>> myCachedMapStorage;
private final TypeEvalContextBasedCache<TypeEvalContext> myCache;
TypeEvalContextCacheImpl(@NotNull final CachedValuesManager manager) {
myCachedMapStorage = manager.createCachedValue(MAP_CREATOR, false);
myCache = new TypeEvalContextBasedCache<TypeEvalContext>(manager, VALUE_PROVIDER);
}
@NotNull
@Override
public TypeEvalContext getContext(@NotNull final TypeEvalContext standard) {
// Map is not thread safe, and "getValue" is not atomic. I do not want several maps to be created.
synchronized (myLock) {
final Map<TypeEvalConstraints, TypeEvalContext> map = myCachedMapStorage.getValue();
final TypeEvalContext context = map.get(standard.getConstraints());
if (context != null) {
return context;
}
map.put(standard.getConstraints(), standard);
return standard;
}
return myCache.getValue(standard);
}
/**
* Provider that creates map to store cache. Map depends on PSI modification
*/
private static final class MapCreator implements CachedValueProvider<Map<TypeEvalConstraints, TypeEvalContext>> {
@Nullable
private static class MyValueProvider implements Function<TypeEvalContext, TypeEvalContext> {
@Override
public Result<Map<TypeEvalConstraints, TypeEvalContext>> compute() {
// This method is called if cache is empty. Create new map for it.
final HashMap<TypeEvalConstraints, TypeEvalContext> map = new HashMap<TypeEvalConstraints, TypeEvalContext>();
return new Result<Map<TypeEvalConstraints, TypeEvalContext>>(map, PsiModificationTracker.MODIFICATION_COUNT);
public TypeEvalContext fun(final TypeEvalContext param) {
// key and value are both context here. If no context stored, then key is stored. Old one is returned otherwise to cache.
return param;
}
}
}
@@ -72,14 +72,25 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
public static final PyClass[] EMPTY_ARRAY = new PyClassImpl[0];
private List<PyTargetExpression> myInstanceAttributes;
/**
* Ancestors cache is lazy because provider ({@link com.jetbrains.python.psi.impl.PyClassImpl.CachedAncestorsProvider}) needs
* {@link #getProject()} which is notavailable during consructor time.
*/
private TypeEvalContextBasedCache<List<PyClassLikeType>> myAncestorsCache;
private volatile Map<String, Property> myPropertyCache;
/**
* Lock to create {@link #myAncestorsCache} in lazy way.
*/
@NotNull
private final Object myAncestorsCacheLock = new Object();
private class CachedAncestorsProvider implements ParameterizedCachedValueProvider<List<PyClassLikeType>, TypeEvalContext> {
/**
* Engine to create list of ancestors based on context
*/
private class CachedAncestorsProvider implements Function<TypeEvalContext, List<PyClassLikeType>> {
@Nullable
@Override
public CachedValueProvider.Result<List<PyClassLikeType>> compute(@NotNull TypeEvalContext context) {
public List<PyClassLikeType> fun(@NotNull TypeEvalContext context) {
List<PyClassLikeType> ancestorTypes;
if (isNewStyleClass(context)) {
try {
@@ -102,10 +113,14 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
else {
ancestorTypes = getOldStyleAncestorTypes(context);
}
return CachedValueProvider.Result.create(ancestorTypes, PsiModificationTracker.MODIFICATION_COUNT);
return ancestorTypes;
}
}
private List<PyTargetExpression> myInstanceAttributes;
private volatile Map<String, Property> myPropertyCache;
private final Key<ParameterizedCachedValue<List<PyClassLikeType>, TypeEvalContext>> myCachedValueKey = Key.create("cached ancestors");
private final CachedAncestorsProvider myCachedAncestorsProvider = new CachedAncestorsProvider();
@@ -135,6 +150,25 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
super(stub, nodeType);
}
/**
* @return ancestors cache. It is created lazyly if needed.
*/
@NotNull
private TypeEvalContextBasedCache<List<PyClassLikeType>> getAncestorsCache() {
if (myAncestorsCache != null) {
return myAncestorsCache;
}
synchronized (myAncestorsCacheLock) {
if (myAncestorsCache == null) {
myAncestorsCache = new TypeEvalContextBasedCache<List<PyClassLikeType>>(
CachedValuesManager.getManager(getProject()),
new CachedAncestorsProvider()
);
}
return myAncestorsCache;
}
}
public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
final ASTNode nameElement = PyUtil.createNewName(this, name);
final ASTNode node = getNameNode();
@@ -1274,9 +1308,7 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
@NotNull
@Override
public List<PyClassLikeType> getAncestorTypes(@NotNull TypeEvalContext context) {
// TODO: Return different cached copies depending on the type eval context parameters
final CachedValuesManager manager = CachedValuesManager.getManager(getProject());
return manager.getParameterizedCachedValue(this, myCachedValueKey, myCachedAncestorsProvider, false, context);
return getAncestorsCache().getValue(context);
}
@Nullable