/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-06 Wolfgang M. Meier
* wolfgang@exist-db.org
* http://exist.sourceforge.net
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package org.exist.storage.cache;
/**
* A cache implementation based on a Least Reference Density (LRD)
* replacement policy.
*
* The class maintains a global reference counter, containing the sum of all
* references in the cache. Each object has a timestamp, which is equal to
* the number of global references at the time, the object has been added to
* the cache.
*
* If the cache is full, the object with the least reference density is removed.
* The reference density is computed as the ratio between the object's reference
* counter and the number of references added since the object has been included
* into the cache, i.e. RC(i) / (GR - TS(i)).
*
* @author wolf
*/
public class LRDCache extends GClockCache {
protected int totalReferences = 0;
private int nextCleanup;
private int maxReferences;
private int ageingPeriod;
public LRDCache(int size, double growthFactor, double growthThreshold, String type) {
super(size, growthFactor, growthThreshold, type);
maxReferences = size * 10000;
ageingPeriod = size * 5000;
}
/* (non-Javadoc)
* @see org.exist.storage.cache.LFUCache#add(org.exist.storage.cache.Cacheable, int)
*/
public void add(Cacheable item, int initialRefCount) {
Cacheable old = (Cacheable) map.get(item.getKey());
if (old != null) {
old.incReferenceCount();
totalReferences++;
} else {
item.setReferenceCount(initialRefCount);
item.setTimestamp(totalReferences);
if (count < size) {
items[count++] = item;
map.put(item.getKey(), item);
used++;
} else
removeOne(item);
totalReferences += initialRefCount;
}
if(totalReferences > maxReferences)
cleanup();
else if (totalReferences > nextCleanup)
ageReferences();
}
/* (non-Javadoc)
* @see org.exist.storage.cache.LFUCache#removeOne(org.exist.storage.cache.Cacheable)
*/
protected Cacheable removeOne(Cacheable item) {
Cacheable old;
double rd = 0, minRd = -1;
int bucket = -1;
final int len = items.length;
for (int i = 0; i < len; i++) {
old = items[i];
if (old == null) {
bucket = i;
break;
} else {
// calculate the reference density
rd =
old.getReferenceCount()
/ (double)(totalReferences - old.getTimestamp());
if ((minRd < 0 || rd < minRd) && old.allowUnload()) {
minRd = rd;
bucket = i;
}
}
}
if (bucket < 0)
bucket = 0;
old = items[bucket];
if (old != null) {
map.remove(old.getKey());
old.sync(true);
} else {
used++;
}
items[bucket] = item;
map.put(item.getKey(), item);
if (old != null) {
accounting.replacedPage(item);
if (cacheManager != null && accounting.resizeNeeded()) {
// accounting.stats();
cacheManager.requestMem(this);
}
}
return old;
}
/**
* Periodically adjust items with large reference counts to give
* younger items a chance to survive.
*/
protected void ageReferences() {
Cacheable item;
int refCount;
int limit = ageingPeriod / 10;
for(int i = 0; i < count; i++) {
item = items[i];
if(item != null) {
refCount = item.getReferenceCount();
if(refCount > limit) {
item.setReferenceCount(refCount - limit);
} else
item.setReferenceCount(1);
}
}
nextCleanup += ageingPeriod;
}
/**
* Periodically reset all reference counts to 1.
*/
protected void cleanup() {
Cacheable item;
LOG.debug("totalReferences = " + totalReferences + "; maxReferences = " + maxReferences);
totalReferences = count;
for(int i = 0; i < count; i++) {
item = items[i];
if(item != null) {
item.setReferenceCount(1);
item.setTimestamp(1);
}
}
nextCleanup = totalReferences + ageingPeriod;
}
}