Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend the memo feature with optional parameters for expiring entries #1424

Merged
merged 14 commits into from
Jun 12, 2020
Merged
95 changes: 69 additions & 26 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);
getMemoizationCache(true).store(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
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 {
Thread.sleep(TimeUnit.SECONDS.toMillis(seconds.longValue()));
DavyLandman marked this conversation as resolved.
Show resolved Hide resolved
}
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
;