Skip to content

Commit a7a16e1

Browse files
committed
approximate LRU cache
1 parent 76dd9e9 commit a7a16e1

File tree

1 file changed

+98
-12
lines changed

1 file changed

+98
-12
lines changed

Diff for: Count/src/com/mzlabs/count/op/SolnCache.java

+98-12
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,40 @@
33
import java.math.BigInteger;
44
import java.util.ArrayList;
55
import java.util.Arrays;
6+
import java.util.Date;
67
import java.util.Map;
78
import java.util.TreeMap;
89

910
import com.mzlabs.count.util.IntVec;
1011

1112
public final class SolnCache {
1213

14+
private static final class StoreEntry {
15+
public final int[] key;
16+
public final BigInteger value;
17+
public long lastAccess;
18+
19+
public StoreEntry(final int[] key, final BigInteger value, final long lastAccess) {
20+
this.key = key;
21+
this.value = value;
22+
this.lastAccess = lastAccess;
23+
}
24+
}
25+
1326
private final int nStores = 1377;
14-
private final ArrayList<Map<int[],BigInteger>> hotStores = new ArrayList<Map<int[],BigInteger>>(nStores);
27+
private final long bigSize = 5000000;
28+
private final int initialSizeTargetI = (int)(bigSize/nStores) + 5;
29+
private final long baseTimeMS;
30+
private final ArrayList<Map<int[],StoreEntry>> hotStores = new ArrayList<Map<int[],StoreEntry>>(nStores); // sub-stores (to shallow trees), also sync on these
31+
private final int[] storeBound = new int[nStores]; // heuristic triggers for expensive attempted cache shrink
1532

1633

17-
public SolnCache() {
34+
public SolnCache() {
35+
baseTimeMS = System.currentTimeMillis();
1836
for(int i=0;i<nStores;++i) {
19-
hotStores.add(new TreeMap<int[],BigInteger>(IntVec.IntComp));
37+
hotStores.add(new TreeMap<int[],StoreEntry>(IntVec.IntComp));
2038
}
39+
clear(); // get into initial state
2140
}
2241

2342
/**
@@ -28,53 +47,120 @@ public SolnCache() {
2847
*/
2948
public BigInteger evalCached(final CachableCalculation f, final int[] xin) {
3049
// find the sub-store
31-
final Map<int[],BigInteger> hotStore;
50+
final long time = System.currentTimeMillis();
51+
final int storeIndex;
52+
final Map<int[],StoreEntry> hotStore;
3253
{
3354
int subi = Arrays.hashCode(xin)%nStores;
3455
if(subi<0) {
3556
subi += nStores;
3657
}
37-
hotStore = hotStores.get(subi);
58+
storeIndex = subi;
59+
hotStore = hotStores.get(storeIndex);
3860
}
3961
// hope for cached
4062
synchronized (hotStore) {
41-
final BigInteger found = hotStore.get(xin);
63+
final StoreEntry found = hotStore.get(xin);
4264
if(null!=found) {
43-
return found;
65+
found.lastAccess = time;
66+
return found.value;
4467
}
4568
}
4669
// do the work (while not holding locks)
4770
final int[] xcopy = Arrays.copyOf(xin,xin.length);
4871
final BigInteger value = f.eval(xcopy);
72+
final StoreEntry entry = new StoreEntry(xcopy,value,time);
4973
// write back result
74+
boolean considerReorg = false;
5075
synchronized (hotStore) {
51-
hotStore.put(xcopy,value);
76+
hotStore.put(xcopy,entry);
77+
considerReorg = hotStore.size()>=storeBound[storeIndex];
78+
}
79+
if(considerReorg) {
80+
possiblyReorg();
5281
}
5382
return value;
5483
}
5584

5685
/**
57-
* best effort, not atomic across stores
86+
* assumes no locks held when this is called
87+
* expensive chech and re-org. amortize this to cheap by using double per-sub store count guards.
88+
* trying to be a amortized cheap approximate LRU
89+
*/
90+
private void possiblyReorg() {
91+
synchronized (this) { // there may be other operations working in parallel to this reorg, but no other reorg
92+
// estimate sizes and reset individual triggers
93+
long szEst = 0;
94+
for(int i=0;i<nStores;++i) {
95+
final Map<int[],StoreEntry> s = hotStores.get(i);
96+
synchronized (s) {
97+
final int szi = s.size();
98+
szEst += szi;
99+
storeBound[i] = 2*szi + initialSizeTargetI; // move trigger up to prevent a soon re-trigger
100+
}
101+
}
102+
if(szEst<=bigSize) {
103+
return; // total size still in bounds, take no more action
104+
}
105+
// total size too large, shrink the stores, reset the triggers
106+
// use mean access time as a approximation for median access time
107+
long sumTimes = 0;
108+
long totEntries = 0;
109+
for(int i=0;i<nStores;++i) {
110+
final Map<int[],StoreEntry> s = hotStores.get(i);
111+
synchronized (s) {
112+
for(final StoreEntry v: s.values()) {
113+
sumTimes += v.lastAccess-baseTimeMS;
114+
totEntries += 1;
115+
}
116+
}
117+
}
118+
final long meanTime = (long)Math.ceil(sumTimes/(double)totEntries);
119+
// prune stores and reset triggers
120+
long newSize = 0;
121+
for(int i=0;i<nStores;++i) {
122+
final Map<int[],StoreEntry> s = hotStores.get(i);
123+
synchronized (s) {
124+
final StoreEntry[] vals = s.values().toArray(new StoreEntry[s.size()]);
125+
s.clear();
126+
for(final StoreEntry v: vals) {
127+
if(v.lastAccess-baseTimeMS>meanTime) {
128+
s.put(v.key,v);
129+
}
130+
}
131+
final int szi = s.size();
132+
newSize += szi;
133+
storeBound[i] = 2*szi + initialSizeTargetI; // move trigger up to prevent a soon re-trigger
134+
}
135+
}
136+
System.err.println("#\tinfo: reorg stores " + szEst + " -> " + newSize + "\t" + new Date());
137+
}
138+
}
139+
140+
/**
141+
* best effort, not atomic across stores; but still fairly expensive because of lock acquisition costs
58142
* @return
59143
*/
60144
public long size() {
61145
long size = 0;
62-
for(final Map<int[],BigInteger> s: hotStores) {
146+
for(int i=0;i<nStores;++i) {
147+
final Map<int[],StoreEntry> s = hotStores.get(i);
63148
synchronized (s) {
64149
size += s.size();
65150
}
66151
}
67152
return size;
68153
}
69-
70154

71155
/**
72156
* best effort, not atomic across stores
73157
*/
74158
public void clear() {
75-
for(final Map<int[],BigInteger> s: hotStores) {
159+
for(int i=0;i<nStores;++i) {
160+
final Map<int[],StoreEntry> s = hotStores.get(i);
76161
synchronized (s) {
77162
s.clear();
163+
storeBound[i] = initialSizeTargetI;
78164
}
79165
}
80166
}

0 commit comments

Comments
 (0)