#include "stdinc.h" #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; Rendez full; IHash *hash; IEntry *entries; int nentries; IEntry free; IEntry clean; IEntry dirty; u32int maxdirty; u32int ndirty; AState as; ISum **sum; int nsum; IHash *shash; IEntry *sentries; int nsentries; }; static ICache icache; /* * Hash table of IEntries */ struct IHash { int bits; u32int size; IEntry **table; }; static IHash* mkihash(int size1) { 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; } static IEntry* ihashlookup(IHash *ih, u8int score[VtScoreSize], int type) { 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 ihashdelete(IHash *ih, IEntry *ie, char *what) { 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); } static void ihashinsert(IHash *ih, IEntry *ie) { u32int h; h = hashbits(ie->score, ih->bits); ie->nexthash = ih->table[h]; ih->table[h] = ie; } /* * IEntry lists. */ static IEntry* popout(IEntry *ie) { 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; } static IEntry* poplast(IEntry *list) { if(list->prev == list) return nil; return popout(list->prev); } 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; } /* * Arena summary cache. */ struct ISum { QLock lock; IEntry *entries; int nentries; int loaded; u64int addr; u64int limit; Arena *arena; int g; }; static ISum* scachelookup(u64int addr) { int i; ISum *s; for(i=0; iaddr <= 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; inentries; 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; 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; } static void scachehit(u64int addr) { scachelookup(addr); /* for move-to-front */ } 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; ientries[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 */ } /* * Index cache. */ void initicache(u32int mem0) { u32int mem; int i, entries, scache; icache.full.l = &icache.lock; 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; ientries = icache.sentries + i*ArenaCIGSize; } } 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(); pushfirst(&icache.dirty, ie); } ihashinsert(icache.hash, ie); } int icachelookup(u8int score[VtScoreSize], int type, IAddr *ia) { IEntry *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); return -1; } int insertscore(u8int score[VtScoreSize], IAddr *ia, int state, AState *as) { ISum *toload; qlock(&icache.lock); icacheinsert(score, ia, state); if(state == IEClean) toload = scachemiss(ia->addr); else{ assert(state == IEDirty); toload = nil; if(as == nil) fprint(2, "%T insertscore IEDirty without as; called from %#p\n", getcallerpc(&score)); else{ if(icache.as.aa > as->aa) fprint(2, "%T insertscore: aa moving backward: %#llux -> %#llux\n", icache.as.aa, as->aa); icache.as = *as; } } qunlock(&icache.lock); if(toload){ scacheload(toload); qunlock(&toload->lock); } if(icache.ndirty >= icache.maxdirty) kickicache(); /* * It's okay not to do this under icache.lock. * Calling insertscore only happens when we hold * the lump, meaning any searches for this block * will hit in the lump cache until after we return. */ if(state == IEDirty) markbloomfilter(mainindex->bloom, score); return 0; } static int lookupscore_untimed(u8int score[VtScoreSize], int type, IAddr *ia) { IEntry d; if(icachelookup(score, type, ia) >= 0) return 0; addstat(StatIcacheFill, 1); if(loadientry(mainindex, score, type, &d) < 0) return -1; insertscore(score, &d.ia, IEClean, nil); *ia = d.ia; return 0; } 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; 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) { u32int h; IEntry *ie, *dirty; dirty = nil; trace(TraceProc, "icachedirty enter"); qlock(&icache.lock); 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) flushdcache(); return dirty; } AState icachestate(void) { AState as; qlock(&icache.lock); as = icache.as; qunlock(&icache.lock); return as; } /* * 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) { IEntry *next; trace(TraceProc, "icacheclean enter"); qlock(&icache.lock); for(; ie; ie=next){ assert(ie->state == IEDirty); next = ie->nextdirty; ie->nextdirty = nil; popout(ie); /* from icache.dirty */ icache.ndirty--; ie->state = IEClean; pushfirst(&icache.clean, ie); } setstat(StatIcacheDirty, icache.ndirty); rwakeupall(&icache.full); qunlock(&icache.lock); trace(TraceProc, "icacheclean exit"); } void emptyicache(void) { int i; IEntry *ie; ISum *s; qlock(&icache.lock); while((ie = evictlru()) != nil) pushfirst(&icache.free, ie); for(i=0; ilock); sumclear(s); qunlock(&s->lock); } qunlock(&icache.lock); }