aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/venti/srv/icache.c
diff options
context:
space:
mode:
authorRuss Cox <rsc@swtch.com>2007-09-25 09:47:31 -0400
committerRuss Cox <rsc@swtch.com>2007-09-25 09:47:31 -0400
commit7a400ee957a0815287af806e18ef90dd18b47f82 (patch)
tree023076fb829f630384f2f394eb9577a81fdca59e /src/cmd/venti/srv/icache.c
parent25a4e89fa907ed5a5f5d84eccfb66180007d9c68 (diff)
downloadplan9port-7a400ee957a0815287af806e18ef90dd18b47f82.tar.gz
plan9port-7a400ee957a0815287af806e18ef90dd18b47f82.tar.bz2
plan9port-7a400ee957a0815287af806e18ef90dd18b47f82.zip
venti: new icache
Diffstat (limited to 'src/cmd/venti/srv/icache.c')
-rw-r--r--src/cmd/venti/srv/icache.c708
1 files changed, 431 insertions, 277 deletions
diff --git a/src/cmd/venti/srv/icache.c b/src/cmd/venti/srv/icache.c
index 32cbb5d3..384fd2c1 100644
--- a/src/cmd/venti/srv/icache.c
+++ b/src/cmd/venti/srv/icache.c
@@ -2,236 +2,421 @@
#include "dat.h"
#include "fns.h"
+int icacheprefetch = 1;
+
typedef struct ICache ICache;
+typedef struct IHash IHash;
+typedef struct ISum ISum;
+
struct ICache
{
- QLock lock; /* locks hash table & all associated data */
+ QLock lock;
Rendez full;
- IEntry **heads; /* heads of all the hash chains */
- int bits; /* bits to use for indexing heads */
- u32int size; /* number of heads; == 1 << bits, should be < entries */
- IEntry *base; /* all allocated hash table entries */
- IEntry *free;
- u32int entries; /* elements in base */
- IEntry *dirty; /* chain of dirty elements */
- u32int ndirty;
+ IHash *hash;
+ IEntry *entries;
+ int nentries;
+ IEntry free;
+ IEntry clean;
+ IEntry dirty;
u32int maxdirty;
- u32int unused; /* index of first unused element in base */
- u32int stolen; /* last head from which an element was stolen */
+ u32int ndirty;
- Arena *last[4];
- Arena *lastload;
- int nlast;
+ ISum **sum;
+ int nsum;
+ IHash *shash;
+ IEntry *sentries;
+ int nsentries;
};
-int icacheprefetch = 1;
-
static ICache icache;
-static IEntry *icachealloc(IAddr *ia, u8int *score);
-
/*
- * bits is the number of bits in the icache hash table
- * depth is the average depth
- * memory usage is about (1<<bits) * depth * sizeof(IEntry) + (1<<bits) * sizeof(IEntry*)
+ * Hash table of IEntries
*/
-void
-initicache(int bits, int depth)
+
+struct IHash
{
- icache.bits = bits;
- icache.size = 1 << bits;
- icache.entries = depth * icache.size;
- icache.maxdirty = icache.entries/2;
- icache.base = MKNZ(IEntry, icache.entries);
- icache.heads = MKNZ(IEntry*, icache.size);
- icache.full.l = &icache.lock;
- setstat(StatIcacheSize, icache.entries);
-}
+ int bits;
+ u32int size;
+ IEntry **table;
+};
-ulong
-icachedirtyfrac(void)
+static IHash*
+mkihash(int size1)
{
- return (vlong)icache.ndirty*IcacheFrac / icache.entries;
+ u32int size;
+ int bits;
+ IHash *ih;
+
+ bits = 0;
+ size = 1;
+ while(size < size1){
+ bits++;
+ size <<= 1;
+ }
+
+ ih = vtmallocz(sizeof(IHash)+size*sizeof(ih->table[0]));
+ ih->table = (IEntry**)(ih+1);
+ ih->bits = bits;
+ ih->size = size;
+ return ih;
}
-u32int
-hashbits(u8int *sc, int bits)
+static IEntry*
+ihashlookup(IHash *ih, u8int score[VtScoreSize], int type)
{
- u32int v;
-
- v = (sc[0] << 24) | (sc[1] << 16) | (sc[2] << 8) | sc[3];
- if(bits < 32)
- v >>= (32 - bits);
- return v;
+ u32int h;
+ IEntry *ie;
+
+ h = hashbits(score, ih->bits);
+ for(ie=ih->table[h]; ie; ie=ie->nexthash)
+ if((type == -1 || type == ie->ia.type) && scorecmp(score, ie->score) == 0)
+ return ie;
+ return nil;
}
static void
-loadarenaclumps(Arena *arena, u64int aa)
+ihashdelete(IHash *ih, IEntry *ie, char *what)
{
- ulong i;
- ClumpInfo ci;
- IAddr ia;
-
- for(i=0; i<arena->memstats.clumps; i++){
- if(readclumpinfo(arena, i, &ci) < 0)
- break;
- ia.type = ci.type;
- ia.size = ci.uncsize;
- ia.blocks = (ci.size + ClumpSize + (1 << ABlockLog) - 1) >> ABlockLog;
- ia.addr = aa;
- aa += ClumpSize + ci.size;
- if(ia.type != VtCorruptType)
- insertscore(ci.score, &ia, 0);
- }
+ u32int h;
+ IEntry **l;
+
+ h = hashbits(ie->score, ih->bits);
+ for(l=&ih->table[h]; *l; l=&(*l)->nexthash)
+ if(*l == ie){
+ *l = ie->nexthash;
+ return;
+ }
+ fprint(2, "warning: %s %V not found in ihashdelete\n", what, ie->score);
}
-int
-_lookupscore(u8int *score, int type, IAddr *ia, int *rac)
+static void
+ihashinsert(IHash *ih, IEntry *ie)
{
u32int h;
- IEntry *ie, *last;
-
- qlock(&icache.lock);
- h = hashbits(score, icache.bits);
- last = nil;
- for(ie = icache.heads[h]; ie != nil; ie = ie->next){
- if((ie->ia.type == type || type == -1) && scorecmp(ie->score, score)==0){
- if(last != nil)
- last->next = ie->next;
- else
- icache.heads[h] = ie->next;
- addstat(StatIcacheHit, 1);
- if(rac)
- ie->rac = 1;
- trace(TraceLump, "lookupscore incache");
- ie->next = icache.heads[h];
- icache.heads[h] = ie;
-
- *ia = ie->ia;
- if(rac)
- *rac = ie->rac;
- qunlock(&icache.lock);
- return 0;
- }
- last = ie;
- }
- addstat(StatIcacheMiss, 1);
- qunlock(&icache.lock);
- return -1;
+
+ h = hashbits(ie->score, ih->bits);
+ ie->nexthash = ih->table[h];
+ ih->table[h] = ie;
}
/*
-ZZZ need to think about evicting the correct IEntry,
-and writing back the wtime.
- * look up data score in the index cache
- * if this fails, pull it in from the disk index table, if it exists.
- *
- * must be called with the lump for this score locked
+ * IEntry lists.
*/
-int
-lookupscore(u8int *score, int type, IAddr *ia, int *rac)
+
+static IEntry*
+popout(IEntry *ie)
{
- IEntry d, *ie;
- u32int h;
- u64int aa;
- Arena *load;
- int i, ret;
- uint ms;
+ if(ie->prev == nil && ie->next == nil)
+ return ie;
+ ie->prev->next = ie->next;
+ ie->next->prev = ie->prev;
+ ie->next = nil;
+ ie->prev = nil;
+ return ie;
+}
- aa = 0;
- ms = msec();
-
- trace(TraceLump, "lookupscore %V.%d", score, type);
+static IEntry*
+poplast(IEntry *list)
+{
+ if(list->prev == list)
+ return nil;
+ return popout(list->prev);
+}
- ret = 0;
- if(_lookupscore(score, type, ia, rac) < 0){
- if(loadientry(mainindex, score, type, &d) < 0){
- ret = -1;
- goto out;
- }
+static IEntry*
+pushfirst(IEntry *list, IEntry *ie)
+{
+ popout(ie);
+ ie->prev = list;
+ ie->next = list->next;
+ ie->prev->next = ie;
+ ie->next->prev = ie;
+ return ie;
+}
- /* failed in cache but found on disk - fill cache. */
- trace(TraceLump, "lookupscore loaded");
- addstat(StatIcacheFill, 1);
+/*
+ * Arena summary cache.
+ */
+struct ISum
+{
+ QLock lock;
+ IEntry *entries;
+ int nentries;
+ int loaded;
+ u64int addr;
+ u64int limit;
+ Arena *arena;
+ int g;
+};
- /*
- * no one else can load an entry for this score,
- * since we have this score's lump's lock.
- */
- qlock(&icache.lock);
-
- /*
- * If we notice that all the hits are coming from one arena,
- * load the table of contents for that arena into the cache.
- */
- load = nil;
- h = hashbits(score, icache.bits);
- ie = icachealloc(&d.ia, score);
- if(icacheprefetch){
- icache.last[icache.nlast++%nelem(icache.last)] = amapitoa(mainindex, ie->ia.addr, &aa);
- aa = ie->ia.addr - aa; /* compute base addr of arena */
- for(i=0; i<nelem(icache.last); i++)
- if(icache.last[i] != icache.last[0])
- break;
- if(i==nelem(icache.last) && icache.lastload != icache.last[0]){
- load = icache.last[0];
- icache.lastload = load;
+static ISum*
+scachelookup(u64int addr)
+{
+ int i;
+ ISum *s;
+
+ for(i=0; i<icache.nsum; i++){
+ s = icache.sum[i];
+ if(s->addr <= addr && addr < s->limit){
+ if(i > 0){
+ memmove(icache.sum+1, icache.sum, i*sizeof icache.sum[0]);
+ icache.sum[0] = s;
}
+ return s;
}
+ }
+ return nil;
+}
+
+static void
+sumclear(ISum *s)
+{
+ int i;
+
+ for(i=0; i<s->nentries; i++)
+ ihashdelete(icache.shash, &s->entries[i], "scache");
+ s->nentries = 0;
+ s->loaded = 0;
+ s->addr = 0;
+ s->limit = 0;
+ s->arena = nil;
+ s->g = 0;
+}
+
+static ISum*
+scacheevict(void)
+{
+ ISum *s;
+ int i;
- ie->next = icache.heads[h];
- icache.heads[h] = ie;
-
- *ia = ie->ia;
- *rac = ie->rac;
-
- qunlock(&icache.lock);
- if(load){
- trace(TraceProc, "preload 0x%llux", aa);
- loadarenaclumps(load, aa);
+ for(i=icache.nsum-1; i>=0; i--){
+ s = icache.sum[i];
+ if(canqlock(&s->lock)){
+ if(i > 0){
+ memmove(icache.sum+1, icache.sum, i*sizeof icache.sum[0]);
+ icache.sum[0] = s;
+ }
+ sumclear(s);
+ return s;
}
}
+ return nil;
+}
-out:
- ms = msec() - ms;
- addstat2(StatIcacheRead, 1, StatIcacheReadTime, ms);
+static void
+scachehit(u64int addr)
+{
+ scachelookup(addr); /* for move-to-front */
+}
- return ret;
+static void
+scachesetup(ISum *s, u64int addr)
+{
+ u64int addr0, limit;
+ int g;
+
+ s->arena = amapitoag(mainindex, addr, &addr0, &limit, &g);
+ s->addr = addr0;
+ s->limit = limit;
+ s->g = g;
+}
+
+static void
+scacheload(ISum *s)
+{
+ int i, n;
+
+ s->loaded = 1;
+ n = asumload(s->arena, s->g, s->entries, ArenaCIGSize);
+ /*
+ * n can be less then ArenaCIGSize, either if the clump group
+ * is the last in the arena and is only partially filled, or if there
+ * are corrupt clumps in the group -- those are not returned.
+ */
+ for(i=0; i<n; i++){
+ s->entries[i].ia.addr += s->addr;
+ ihashinsert(icache.shash, &s->entries[i]);
+ }
+//fprint(2, "%T scacheload %s %d - %d entries\n", s->arena->name, s->g, n);
+ addstat(StatScachePrefetch, n);
+ s->nentries = n;
+}
+
+static ISum*
+scachemiss(u64int addr)
+{
+ ISum *s;
+
+ s = scachelookup(addr);
+ if(s == nil){
+ /* first time: make an entry in the cache but don't populate it yet */
+ s = scacheevict();
+ if(s == nil)
+ return nil;
+ scachesetup(s, addr);
+ qunlock(&s->lock);
+ return nil;
+ }
+
+ /* second time: load from disk */
+ qlock(&s->lock);
+ if(s->loaded || !icacheprefetch){
+ qunlock(&s->lock);
+ return nil;
+ }
+
+ return s; /* locked */
}
/*
- * insert a new element in the hash table.
+ * Index cache.
*/
-int
-insertscore(u8int *score, IAddr *ia, int write)
+
+void
+initicache(u32int mem0)
{
- IEntry *ie, se;
- u32int h;
+ u32int mem;
+ int i, entries, scache;
+
+ icache.full.l = &icache.lock;
- trace(TraceLump, "insertscore enter");
- if(write)
- addstat(StatIcacheWrite, 1);
- else
- addstat(StatIcachePrefetch, 1);
+ mem = mem0;
+ entries = mem / (sizeof(IEntry)+sizeof(IEntry*));
+ scache = (entries/8) / ArenaCIGSize;
+ entries -= entries/8;
+ if(scache < 4)
+ scache = 4;
+ if(scache > 16)
+ scache = 16;
+ if(entries < 1000)
+ entries = 1000;
+fprint(2, "icache %,d bytes = %,d entries; %d scache\n", mem0, entries, scache);
+
+ icache.clean.prev = icache.clean.next = &icache.clean;
+ icache.dirty.prev = icache.dirty.next = &icache.dirty;
+ icache.free.prev = icache.free.next = &icache.free;
+
+ icache.hash = mkihash(entries);
+ icache.nentries = entries;
+ setstat(StatIcacheSize, entries);
+ icache.entries = vtmallocz(entries*sizeof icache.entries[0]);
+ icache.maxdirty = entries / 2;
+ for(i=0; i<entries; i++)
+ pushfirst(&icache.free, &icache.entries[i]);
+
+ icache.nsum = scache;
+ icache.sum = vtmallocz(scache*sizeof icache.sum[0]);
+ icache.sum[0] = vtmallocz(scache*sizeof icache.sum[0][0]);
+ icache.nsentries = scache * ArenaCIGSize;
+ icache.sentries = vtmallocz(scache*ArenaCIGSize*sizeof icache.sentries[0]);
+ icache.shash = mkihash(scache*ArenaCIGSize);
+ for(i=0; i<scache; i++){
+ icache.sum[i] = icache.sum[0] + i;
+ icache.sum[i]->entries = icache.sentries + i*ArenaCIGSize;
+ }
+}
- qlock(&icache.lock);
- h = hashbits(score, icache.bits);
- ie = icachealloc(ia, score);
- if(write){
+static IEntry*
+evictlru(void)
+{
+ IEntry *ie;
+
+ ie = poplast(&icache.clean);
+ if(ie == nil)
+ return nil;
+ ihashdelete(icache.hash, ie, "evictlru");
+ return ie;
+}
+
+static void
+icacheinsert(u8int score[VtScoreSize], IAddr *ia, int state)
+{
+ IEntry *ie;
+
+ if((ie = poplast(&icache.free)) == nil && (ie = evictlru()) == nil){
+ addstat(StatIcacheStall, 1);
+ while((ie = poplast(&icache.free)) == nil && (ie = evictlru()) == nil){
+ // Could safely return here if state == IEClean.
+ // But if state == IEDirty, have to wait to make
+ // sure we don't lose an index write.
+ // Let's wait all the time.
+ flushdcache();
+ kickicache();
+ rsleep(&icache.full);
+ }
+ addstat(StatIcacheStall, -1);
+ }
+
+ memmove(ie->score, score, VtScoreSize);
+ ie->state = state;
+ ie->ia = *ia;
+ if(state == IEClean){
+ addstat(StatIcachePrefetch, 1);
+ pushfirst(&icache.clean, ie);
+ }else{
+ addstat(StatIcacheWrite, 1);
+ assert(state == IEDirty);
icache.ndirty++;
setstat(StatIcacheDirty, icache.ndirty);
delaykickicache();
- ie->dirty = 1;
+ pushfirst(&icache.dirty, ie);
}
- ie->next = icache.heads[h];
- icache.heads[h] = ie;
+ ihashinsert(icache.hash, ie);
+}
+
+int
+icachelookup(u8int score[VtScoreSize], int type, IAddr *ia)
+{
+ IEntry *ie;
- se = *ie;
+ qlock(&icache.lock);
+ addstat(StatIcacheLookup, 1);
+ if((ie = ihashlookup(icache.hash, score, type)) != nil){
+ *ia = ie->ia;
+ if(ie->state == IEClean)
+ pushfirst(&icache.clean, ie);
+ addstat(StatIcacheHit, 1);
+ qunlock(&icache.lock);
+ return 0;
+ }
+
+ if((ie = ihashlookup(icache.shash, score, type)) != nil){
+ *ia = ie->ia;
+ icacheinsert(score, &ie->ia, IEClean);
+ scachehit(ie->ia.addr);
+ addstat(StatScacheHit, 1);
+ qunlock(&icache.lock);
+ return 0;
+ }
+ addstat(StatIcacheMiss, 1);
qunlock(&icache.lock);
- if(write && icache.ndirty >= icache.maxdirty)
+ return -1;
+}
+
+int
+insertscore(u8int score[VtScoreSize], IAddr *ia, int state)
+{
+ ISum *toload;
+
+ qlock(&icache.lock);
+ icacheinsert(score, ia, state);
+ if(state == IEClean)
+ toload = scachemiss(ia->addr);
+ else{
+ assert(state == IEDirty);
+ toload = nil;
+ }
+ qunlock(&icache.lock);
+ if(toload){
+ scacheload(toload);
+ qunlock(&toload->lock);
+ }
+
+ if(icache.ndirty >= icache.maxdirty)
kickicache();
/*
@@ -240,125 +425,81 @@ insertscore(u8int *score, IAddr *ia, int write)
* the lump, meaning any searches for this block
* will hit in the lump cache until after we return.
*/
- markbloomfilter(mainindex->bloom, score);
+ if(state == IEDirty)
+ markbloomfilter(mainindex->bloom, score);
return 0;
}
-/*
- * allocate a index cache entry which hasn't been used in a while.
- * must be called with icache.lock locked
- * if the score is already in the table, update the entry.
- */
-static IEntry *
-icachealloc(IAddr *ia, u8int *score)
+static int
+lookupscore_untimed(u8int score[VtScoreSize], int type, IAddr *ia)
{
- int i;
- IEntry *ie, *last, *clean, *lastclean;
- u32int h;
+ IEntry d;
- h = hashbits(score, icache.bits);
- last = nil;
- for(ie = icache.heads[h]; ie != nil; ie = ie->next){
- if(ie->ia.type == ia->type && scorecmp(ie->score, score)==0){
- if(last != nil)
- last->next = ie->next;
- else
- icache.heads[h] = ie->next;
- trace(TraceLump, "icachealloc hit");
- ie->rac = 1;
- return ie;
- }
- last = ie;
- }
+ if(icachelookup(score, type, ia) >= 0)
+ return 0;
- h = icache.unused;
- if(h < icache.entries){
- ie = &icache.base[h++];
- icache.unused = h;
- trace(TraceLump, "icachealloc unused");
- goto Found;
- }
+ addstat(StatIcacheFill, 1);
+ if(loadientry(mainindex, score, type, &d) < 0)
+ return -1;
- if((ie = icache.free) != nil){
- icache.free = ie->next;
- goto Found;
- }
+ insertscore(score, &d.ia, IEClean);
+ *ia = d.ia;
+ return 0;
+}
- h = icache.stolen;
- for(i=0;; i++){
- h++;
- if(h >= icache.size)
- h = 0;
- if(i == icache.size){
- trace(TraceLump, "icachealloc sleep");
- addstat(StatIcacheStall, 1);
- while(icache.ndirty == icache.entries){
- /*
- * This is a bit suspect. Kickicache will wake up the
- * icachewritecoord, but if all the index entries are for
- * unflushed disk blocks, icachewritecoord won't be
- * able to do much. It always rewakes everyone when
- * it thinks it is done, though, so at least we'll go around
- * the while loop again. Also, if icachewritecoord sees
- * that the disk state hasn't change at all since the last
- * time around, it kicks the disk. This needs to be
- * rethought, but it shouldn't deadlock anymore.
- */
- kickicache();
- rsleep(&icache.full);
- }
- addstat(StatIcacheStall, -1);
- i = 0;
- }
- lastclean = nil;
- clean = nil;
- last = nil;
- for(ie=icache.heads[h]; ie; last=ie, ie=ie->next){
- if(!ie->dirty){
- clean = ie;
- lastclean = last;
- }
- }
- if(clean){
- if(lastclean)
- lastclean->next = clean->next;
- else
- icache.heads[h] = clean->next;
- clean->next = nil;
- icache.stolen = h;
- ie = clean;
- trace(TraceLump, "icachealloc steal");
- goto Found;
- }
- }
+int
+lookupscore(u8int score[VtScoreSize], int type, IAddr *ia)
+{
+ int ms, ret;
+
+ ms = msec();
+ ret = lookupscore_untimed(score, type, ia);
+ ms = msec() - ms;
+ addstat2(StatIcacheRead, 1, StatIcacheReadTime, ms);
+ return ret;
+}
+
+u32int
+hashbits(u8int *sc, int bits)
+{
+ u32int v;
-Found:
- ie->ia = *ia;
- scorecp(ie->score, score);
- ie->rac = 0;
- return ie;
+ v = (sc[0] << 24) | (sc[1] << 16) | (sc[2] << 8) | sc[3];
+ if(bits < 32)
+ v >>= (32 - bits);
+ return v;
}
+ulong
+icachedirtyfrac(void)
+{
+ return (vlong)icache.ndirty*IcacheFrac / icache.nentries;
+}
+
+/*
+ * Return a singly-linked list of dirty index entries.
+ * with 32-bit hash numbers between lo and hi
+ * and address < limit.
+ */
IEntry*
icachedirty(u32int lo, u32int hi, u64int limit)
{
- int i;
u32int h;
IEntry *ie, *dirty;
dirty = nil;
trace(TraceProc, "icachedirty enter");
qlock(&icache.lock);
- for(i=0; i<icache.size; i++)
- for(ie = icache.heads[i]; ie; ie=ie->next)
- if(ie->dirty && ie->ia.addr != 0 && ie->ia.addr < limit){
+ for(ie = icache.dirty.next; ie != &icache.dirty; ie=ie->next){
+ if(ie->state == IEDirty && ie->ia.addr < limit){
h = hashbits(ie->score, 32);
if(lo <= h && h <= hi){
ie->nextdirty = dirty;
dirty = ie;
}
}
+ }
qunlock(&icache.lock);
trace(TraceProc, "icachedirty exit");
if(dirty == nil)
@@ -366,36 +507,49 @@ icachedirty(u32int lo, u32int hi, u64int limit)
return dirty;
}
+
+/*
+ * The singly-linked non-circular list of index entries ie
+ * has been written to disk. Move them to the clean list.
+ */
void
icacheclean(IEntry *ie)
{
- trace(TraceProc, "icachedirty enter");
+ IEntry *next;
+
+ trace(TraceProc, "icacheclean enter");
qlock(&icache.lock);
- for(; ie; ie=ie->nextdirty){
+ for(; ie; ie=next){
+ assert(ie->state == IEDirty);
+ next = ie->nextdirty;
+ ie->nextdirty = nil;
+ popout(ie); /* from icache.dirty */
icache.ndirty--;
- ie->dirty = 0;
+ ie->state = IEClean;
+ pushfirst(&icache.clean, ie);
}
setstat(StatIcacheDirty, icache.ndirty);
rwakeupall(&icache.full);
qunlock(&icache.lock);
- trace(TraceProc, "icachedirty exit");
+ trace(TraceProc, "icacheclean exit");
}
void
emptyicache(void)
{
int i;
- IEntry *ie, **lie;
+ IEntry *ie;
+ ISum *s;
qlock(&icache.lock);
- for(i=0; i<icache.size; i++)
- for(lie=&icache.heads[i]; (ie=*lie); ){
- if(ie->dirty == 0){
- *lie = ie->next;
- ie->next = icache.free;
- icache.free = ie;
- }else
- lie = &ie->next;
+ while((ie = evictlru()) != nil)
+ pushfirst(&icache.free, ie);
+ for(i=0; i<icache.nsum; i++){
+ s = icache.sum[i];
+ qlock(&s->lock);
+ sumclear(s);
+ qunlock(&s->lock);
}
qunlock(&icache.lock);
}
+