Skip to content

Commit

Permalink
Merge pull request #1424 from usethesource/expering-memo
Browse files Browse the repository at this point in the history
Extend the memo feature with optional parameters for expiring entries
  • Loading branch information
DavyLandman committed Jun 12, 2020
2 parents c37297a + de10cec commit 6dc2ced
Show file tree
Hide file tree
Showing 9 changed files with 636 additions and 57 deletions.
2 changes: 1 addition & 1 deletion src/org/rascalmpl/interpreter/result/JavaMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public Result<IValue> call(Type[] actualTypes, IValue[] actuals, Map<String, IVa
Type resultType = getReturnType().instantiate(env.getTypeBindings());

resultValue = ResultFactory.makeResult(resultType, result, eval);
storeMemoizedResult(actuals, keyArgValues, resultValue);
resultValue = storeMemoizedResult(actuals, keyArgValues, resultValue);
printEndTrace(resultValue.value);
return resultValue;
}
Expand Down
97 changes: 70 additions & 27 deletions src/org/rascalmpl/interpreter/result/NamedFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.rascalmpl.ast.AbstractAST;
import org.rascalmpl.ast.FunctionDeclaration;
Expand All @@ -31,11 +32,13 @@
import org.rascalmpl.interpreter.asserts.ImplementationError;
import org.rascalmpl.interpreter.control_exceptions.MatchFailed;
import org.rascalmpl.interpreter.env.Environment;
import org.rascalmpl.interpreter.result.util.MemoizationCache;
import org.rascalmpl.interpreter.types.FunctionType;
import org.rascalmpl.interpreter.utils.Names;
import org.rascalmpl.util.ExpiringFunctionResultCache;

import io.usethesource.vallang.IConstructor;
import io.usethesource.vallang.IInteger;
import io.usethesource.vallang.ISet;
import io.usethesource.vallang.IValue;
import io.usethesource.vallang.type.Type;

Expand All @@ -49,9 +52,11 @@ abstract public class NamedFunction extends AbstractFunction {
protected final String resourceScheme;
protected final String resolverScheme;
protected final Map<String, IValue> tags;

private SoftReference<MemoizationCache<Result<IValue>>> memoization;
private SoftReference<ExpiringFunctionResultCache<Result<IValue>>> memoization;
protected final boolean hasMemoization;
private final int memoizationTimeout;
private final int memoizationMaxEntries;

public NamedFunction(AbstractAST ast, IEvaluator<Result<IValue>> eval, FunctionType functionType, List<KeywordFormal> initializers, String name,
boolean varargs, boolean isDefault, boolean isTest, Environment env) {
Expand All @@ -65,12 +70,27 @@ public NamedFunction(AbstractAST ast, IEvaluator<Result<IValue>> eval, FunctionT
tags = parseTags((FunctionDeclaration) ast);
this.resourceScheme = getResourceScheme((FunctionDeclaration) ast);
this.resolverScheme = getResolverScheme((FunctionDeclaration) ast);
this.hasMemoization = checkMemoization((FunctionDeclaration) ast);
IValue memoTag = tags.get("memo");
if (memoTag != null) {
this.hasMemoization = true;
if (!(memoTag instanceof ISet)) {
memoTag = vf.set(memoTag);
}
this.memoizationTimeout = getMemoizationTimeout((ISet)memoTag);
this.memoizationMaxEntries = getMemoizationMaxEntries((ISet)memoTag);
}
else {
this.hasMemoization = false;
this.memoizationTimeout = (int) TimeUnit.HOURS.toSeconds(1);
this.memoizationMaxEntries = -1;
}
} else {
tags = new HashMap<String, IValue>();
this.resourceScheme = null;
this.resolverScheme = null;
this.hasMemoization = false;
this.memoizationTimeout = 0;
this.memoizationMaxEntries = 0;
}
}

Expand All @@ -91,7 +111,7 @@ public String getName() {

public void clearMemoizationCache() {
if (memoization != null) {
MemoizationCache<Result<IValue>> m = memoization.get();
ExpiringFunctionResultCache<Result<IValue>> m = memoization.get();

if (m != null) {
m.clear();
Expand All @@ -104,35 +124,25 @@ public void clearMemoizationCache() {

protected Result<IValue> getMemoizedResult(IValue[] argValues, Map<String, IValue> keyArgValues) {
if (hasMemoization()) {
MemoizationCache<Result<IValue>> memoizationActual = getMemoizationCache(false);
if (memoizationActual == null) {
return null;
}
return memoizationActual.getStoredResult(argValues, keyArgValues);
ExpiringFunctionResultCache<Result<IValue>> memoizationActual = getMemoizationCache(false);
return memoizationActual == null ? null : memoizationActual.lookup(argValues, keyArgValues);
}
return null;
}

private MemoizationCache<Result<IValue>> getMemoizationCache(boolean returnFresh) {
MemoizationCache<Result<IValue>> result = null;
if (memoization == null) {
result = new MemoizationCache<Result<IValue>>();
private ExpiringFunctionResultCache<Result<IValue>> getMemoizationCache(boolean returnFresh) {
ExpiringFunctionResultCache<Result<IValue>> result = memoization == null ? null : memoization.get();
if (result == null && returnFresh) {
result = new ExpiringFunctionResultCache<Result<IValue>>(memoizationTimeout, memoizationMaxEntries);
memoization = new SoftReference<>(result);
return returnFresh ? result : null;

}
result = memoization.get();
if (result == null ) {
result = new MemoizationCache<Result<IValue>>();
memoization = new SoftReference<>(result);
return returnFresh ? result : null;
return result;
}
return result;
}

protected Result<IValue> storeMemoizedResult(IValue[] argValues, Map<String, IValue> keyArgValues, Result<IValue> result) {
if (hasMemoization()) {
getMemoizationCache(true).storeResult(argValues, keyArgValues, result);
return getMemoizationCache(true).store(argValues, keyArgValues, result);
}
return result;
}
Expand All @@ -144,7 +154,7 @@ public Result<IValue> call(Type[] argTypes, IValue[] argValues,
Result<IValue> result = getMemoizedResult(argValues, keyArgValues);
if (result == null) {
result = super.call(argTypes, argValues, keyArgValues);
storeMemoizedResult(argValues, keyArgValues, result);
return storeMemoizedResult(argValues, keyArgValues, result);
}
return result;
}
Expand All @@ -157,14 +167,47 @@ protected static String getResolverScheme(FunctionDeclaration declaration) {
return getScheme(RESOLVER_TAG, declaration);
}

protected boolean checkMemoization(FunctionDeclaration func) {
protected Tag checkMemoization(FunctionDeclaration func) {
for (Tag tag : func.getTags().getTags()) {
if (Names.name(tag.getName()).equalsIgnoreCase("memo")) {
return true;
return tag;
}
}
return false;
return null;
}
private int getMemoizationTimeout(ISet memoTags) {
for (IValue memoTag : memoTags) {
if (memoTag instanceof IConstructor && ((IConstructor) memoTag).getName().equals("expireAfter")) {
Map<String, IValue> kwParams = ((IConstructor)memoTag).asWithKeywordParameters().getParameters();
IValue value = kwParams.get("seconds");
if (value instanceof IInteger) {
return ((IInteger)value).intValue();
}
value = kwParams.get("minutes");
if (value instanceof IInteger) {
return (int) TimeUnit.MINUTES.toSeconds(((IInteger)value).intValue());
}
value = kwParams.get("hours");
if (value instanceof IInteger) {
return (int) TimeUnit.HOURS.toSeconds(((IInteger)value).intValue());
}
}
}
return (int) TimeUnit.HOURS.toSeconds(1);
}

private int getMemoizationMaxEntries(ISet memoTags) {
for (IValue memoTag : memoTags) {
if (memoTag instanceof IConstructor && ((IConstructor) memoTag).getName().equals("maximumSize")) {
IValue value = ((IConstructor)memoTag).get("entries");
if (value instanceof IInteger) {
return ((IInteger)value).intValue();
}
}
}
return -1;
}


protected int computeIndexedPosition(AbstractAST node) {
if (ast instanceof FunctionDeclaration) {
Expand Down
2 changes: 1 addition & 1 deletion src/org/rascalmpl/interpreter/result/RascalFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ public Result<IValue> call(Type[] actualTypes, IValue[] actuals, Map<String, IVa
bindKeywordArgs(keyArgValues);

result = runBody();
storeMemoizedResult(actuals,keyArgValues, result);
result = storeMemoizedResult(actuals,keyArgValues, result);
if (callTracing) {
printEndTrace(result.getValue());
}
Expand Down
9 changes: 9 additions & 0 deletions src/org/rascalmpl/library/Prelude.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.PriorityQueue;
import java.util.Random;
import java.util.Set;
Expand Down Expand Up @@ -3775,6 +3776,14 @@ public int compare(IsEqualsAdapter arg0, IsEqualsAdapter arg1) {
return d0 < d1 ? -1 : ((d0 == d1) ? 0 : 1);
}
}

public void sleep(IInteger seconds) {
try {
TimeUnit.SECONDS.sleep(seconds.longValue());
}
catch (InterruptedException e) {
}
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ This module test the memoization feature
}
module lang::rascal::tests::basic::Memoization

import util::Memo;
import Set;

// over the test we can have duplicate random values, so prefix the test run
Expand All @@ -15,6 +16,7 @@ private void call(value _) {
callCount += 1;
}


test bool memoCalledCorrectly(set[value] x) {
callCount = 0;
for (v <- x) {
Expand All @@ -33,4 +35,42 @@ test bool memoCalledCorrectly2(set[value] x) {
}
testCount += 1;
return callCount == size(x);
}
}

test bool memoExpire() {
int callCount2 = 0;

@memo=maximumSize(10)
int call2(int i) {
callCount2 += 1;
return callCount2;
}

for (i <- [0..5]) {
call2(i);
}

for (i <- [0..5]) {
if (call2(i) != i + 1) {
return false;
}
}

for (i <- [10..10000]) {
// this should take long as to at least hit the cache limit cleanup
call2(i);
}

@javaClass{org.rascalmpl.library.Prelude}
java void sleep(int seconds);

sleep(6);

for (i <- [0..5]) {
if (call2(i) == i + 1) {
// should be dropped from cache by now
return false;
}
}
return true;
}
12 changes: 12 additions & 0 deletions src/org/rascalmpl/library/util/Memo.rsc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module util::Memo

data MemoExpireConfiguration
= expireAfter( // expires entries when <x> times has passed since last access
// define only one of these limits
int seconds = -1,
int minutes = -1,
int hours = -1
)
| maximumSize(int entries) // expires entries when the cache get's fuller than a certain size
;

0 comments on commit 6dc2ced

Please sign in to comment.