diff --git a/presto-main/src/main/java/com/facebook/presto/metadata/FunctionManager.java b/presto-main/src/main/java/com/facebook/presto/metadata/FunctionManager.java index 0c93ea1b14a1f..3421594b615ac 100644 --- a/presto-main/src/main/java/com/facebook/presto/metadata/FunctionManager.java +++ b/presto-main/src/main/java/com/facebook/presto/metadata/FunctionManager.java @@ -40,15 +40,22 @@ import com.facebook.presto.spi.type.TypeSignature; import com.facebook.presto.sql.analyzer.FeaturesConfig; import com.facebook.presto.sql.analyzer.TypeSignatureProvider; +import com.facebook.presto.sql.gen.CacheStatsMBean; import com.facebook.presto.sql.tree.QualifiedName; import com.facebook.presto.transaction.TransactionId; import com.facebook.presto.transaction.TransactionManager; import com.facebook.presto.type.TypeRegistry; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Ordering; +import com.google.common.util.concurrent.UncheckedExecutionException; +import org.weakref.jmx.Managed; +import org.weakref.jmx.Nested; import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; @@ -58,6 +65,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -101,6 +109,8 @@ public class FunctionManager private final HandleResolver handleResolver; private final Map functionNamespaces = new ConcurrentHashMap<>(); private final Map> functionNamespaceManagers = new ConcurrentHashMap<>(); + private final LoadingCache functionCache; + private final CacheStatsMBean cacheStatsMBean; @Inject public FunctionManager( @@ -122,6 +132,11 @@ public FunctionManager( } // TODO: Provide a more encapsulated way for TransactionManager to register FunctionNamespaceManager transactionManager.registerFunctionNamespaceManager(BuiltInFunctionNamespaceManager.ID, builtInFunctionNamespaceManager); + this.functionCache = CacheBuilder.newBuilder() + .recordStats() + .maximumSize(1000) + .build(CacheLoader.from(key -> resolveBuiltInFunction(key.functionName, fromTypeSignatures(key.parameterTypes)))); + this.cacheStatsMBean = new CacheStatsMBean(functionCache); } @VisibleForTesting @@ -131,6 +146,13 @@ public FunctionManager(TypeManager typeManager, BlockEncodingSerde blockEncoding this(typeManager, createTestTransactionManager(), blockEncodingSerde, featuresConfig, new HandleResolver()); } + @Managed + @Nested + public CacheStatsMBean getFunctionResolutionCacheStats() + { + return cacheStatsMBean; + } + public void loadFunctionNamespaceManager( String functionNamespaceManagerName, String functionNamespaceManagerId, @@ -237,6 +259,14 @@ public FunctionHandle resolveFunction(Optional transactionId, Qua } public FunctionHandle resolveFunction(Optional transactionId, QualifiedFunctionName functionName, List parameterTypes) + { + if (functionName.getFunctionNamespace().equals(DEFAULT_NAMESPACE) && parameterTypes.stream().noneMatch(TypeSignatureProvider::hasDependency)) { + return lookupCachedFunction(functionName, parameterTypes); + } + return resolveFunctionInternal(transactionId, functionName, parameterTypes); + } + + private FunctionHandle resolveFunctionInternal(Optional transactionId, QualifiedFunctionName functionName, List parameterTypes) { Optional functionNamespaceManagerId = getServingFunctionNamespaceManagerId(functionName.getFunctionNamespace()); if (!functionNamespaceManagerId.isPresent()) { @@ -278,6 +308,13 @@ public FunctionHandle resolveFunction(Optional transactionId, Qua throw new PrestoException(FUNCTION_NOT_FOUND, constructFunctionNotFoundErrorMessage(functionName, parameterTypes, candidates)); } + private FunctionHandle resolveBuiltInFunction(QualifiedFunctionName functionName, List parameterTypes) + { + checkArgument(functionName.getFunctionNamespace().equals(DEFAULT_NAMESPACE), "Expect built-in functions"); + checkArgument(parameterTypes.stream().noneMatch(TypeSignatureProvider::hasDependency), "Expect parameter types not to have dependency"); + return resolveFunctionInternal(Optional.empty(), functionName, parameterTypes); + } + @Override public FunctionMetadata getFunctionMetadata(FunctionHandle functionHandle) { @@ -348,6 +385,9 @@ public FunctionHandle resolveOperator(OperatorType operatorType, List parameterTypes) { QualifiedFunctionName functionName = QualifiedFunctionName.of(DEFAULT_NAMESPACE, name); + if (parameterTypes.stream().noneMatch(TypeSignatureProvider::hasDependency)) { + return lookupCachedFunction(functionName, parameterTypes); + } Collection candidates = builtInFunctionNamespaceManager.getFunctions(Optional.empty(), functionName); return lookupFunction(builtInFunctionNamespaceManager, Optional.empty(), functionName, parameterTypes, candidates); } @@ -368,6 +408,19 @@ public FunctionHandle lookupCast(CastType castType, TypeSignature fromType, Type return builtInFunctionNamespaceManager.getFunctionHandle(Optional.empty(), signature); } + private FunctionHandle lookupCachedFunction(QualifiedFunctionName functionName, List parameterTypes) + { + try { + return functionCache.getUnchecked(new FunctionResolutionCacheKey(functionName, parameterTypes)); + } + catch (UncheckedExecutionException e) { + if (e.getCause() instanceof PrestoException) { + throw (PrestoException) e.getCause(); + } + throw e; + } + } + private FunctionHandle lookupFunction( FunctionNamespaceManager functionNamespaceManager, Optional transactionHandle, @@ -688,4 +741,48 @@ public String toString() .toString(); } } + + private static class FunctionResolutionCacheKey + { + private final QualifiedFunctionName functionName; + private final List parameterTypes; + + private FunctionResolutionCacheKey(QualifiedFunctionName functionName, List parameterTypes) + { + checkArgument(parameterTypes.stream().noneMatch(TypeSignatureProvider::hasDependency), "Only type signatures without dependency can be cached"); + this.functionName = requireNonNull(functionName, "functionName is null"); + this.parameterTypes = requireNonNull(parameterTypes, "parameterTypes is null").stream() + .map(TypeSignatureProvider::getTypeSignature) + .collect(toImmutableList()); + } + + @Override + public int hashCode() + { + return Objects.hash(functionName, parameterTypes); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + FunctionResolutionCacheKey other = (FunctionResolutionCacheKey) obj; + return Objects.equals(this.functionName, other.functionName) && + Objects.equals(this.parameterTypes, other.parameterTypes); + } + + @Override + public String toString() + { + return toStringHelper(this) + .add("functionName", functionName) + .add("parameterTypes", parameterTypes) + .toString(); + } + } }