plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

icache.c (11053B)


      1 #include "stdinc.h"
      2 #include "dat.h"
      3 #include "fns.h"
      4 
      5 int icacheprefetch = 1;
      6 
      7 typedef struct ICache ICache;
      8 typedef struct IHash IHash;
      9 typedef struct ISum ISum;
     10 
     11 struct ICache
     12 {
     13 	QLock	lock;
     14 	Rendez	full;
     15 	IHash	*hash;
     16 	IEntry	*entries;
     17 	int		nentries;
     18 
     19 	/*
     20 	* gcc 4.3 inlines the pushfirst loop in initicache,
     21 	* but the inliner incorrectly deduces that
     22 	* icache.free.next has a constant value
     23 	* throughout the loop.  (In fact, pushfirst
     24 	* assigns to it as ie->prev->next.)
     25 	* Marking it volatile should avoid this bug.
     26 	* The speed of linked list operations is dwarfed
     27 	* by the disk i/o anyway.
     28 	*/
     29 	volatile IEntry free;
     30 
     31 	IEntry	clean;
     32 	IEntry	dirty;
     33 	u32int	maxdirty;
     34 	u32int	ndirty;
     35 	AState	as;
     36 
     37 	ISum	**sum;
     38 	int		nsum;
     39 	IHash	*shash;
     40 	IEntry	*sentries;
     41 	int		nsentries;
     42 };
     43 
     44 static ICache icache;
     45 
     46 /*
     47  * Hash table of IEntries
     48  */
     49 
     50 struct IHash
     51 {
     52 	int bits;
     53 	u32int size;
     54 	IEntry **table;
     55 };
     56 
     57 static IHash*
     58 mkihash(int size1)
     59 {
     60 	u32int size;
     61 	int bits;
     62 	IHash *ih;
     63 
     64 	bits = 0;
     65 	size = 1;
     66 	while(size < size1){
     67 		bits++;
     68 		size <<= 1;
     69 	}
     70 
     71 	ih = vtmallocz(sizeof(IHash));
     72 	ih->table = vtmallocz(size * sizeof(ih->table[0]));
     73 	ih->bits = bits;
     74 	ih->size = size;
     75 	return ih;
     76 }
     77 
     78 static IEntry*
     79 ihashlookup(IHash *ih, u8int score[VtScoreSize], int type)
     80 {
     81 	u32int h;
     82 	IEntry *ie;
     83 
     84 	h = hashbits(score, ih->bits);
     85 	for(ie=ih->table[h]; ie; ie=ie->nexthash)
     86 		if((type == -1 || type == ie->ia.type) && scorecmp(score, ie->score) == 0)
     87 			return ie;
     88 	return nil;
     89 }
     90 
     91 static void
     92 ihashdelete(IHash *ih, IEntry *ie, char *what)
     93 {
     94 	u32int h;
     95 	IEntry **l;
     96 
     97 	h = hashbits(ie->score, ih->bits);
     98 	for(l=&ih->table[h]; *l; l=&(*l)->nexthash)
     99 		if(*l == ie){
    100 			*l = ie->nexthash;
    101 			return;
    102 		}
    103 	fprint(2, "warning: %s %V not found in ihashdelete\n", what, ie->score);
    104 }
    105 
    106 static void
    107 ihashinsert(IHash *ih, IEntry *ie)
    108 {
    109 	u32int h;
    110 
    111 	h = hashbits(ie->score, ih->bits);
    112 	ie->nexthash = ih->table[h];
    113 	ih->table[h] = ie;
    114 }
    115 
    116 
    117 /*
    118  * IEntry lists.
    119  */
    120 
    121 static IEntry*
    122 popout(IEntry *ie)
    123 {
    124 	if(ie->prev == nil && ie->next == nil)
    125 		return ie;
    126 	ie->prev->next = ie->next;
    127 	ie->next->prev = ie->prev;
    128 	ie->next = nil;
    129 	ie->prev = nil;
    130 	return ie;
    131 }
    132 
    133 static IEntry*
    134 poplast(volatile IEntry *list)
    135 {
    136 	if(list->prev == list)
    137 		return nil;
    138 	return popout(list->prev);
    139 }
    140 
    141 static IEntry*
    142 pushfirst(volatile IEntry *list, IEntry *ie)
    143 {
    144 	popout(ie);
    145 	ie->prev = (IEntry*)list;
    146 	ie->next = list->next;
    147 	ie->prev->next = ie;
    148 	ie->next->prev = ie;
    149 	return ie;
    150 }
    151 
    152 /*
    153  * Arena summary cache.
    154  */
    155 struct ISum
    156 {
    157 	QLock	lock;
    158 	IEntry	*entries;
    159 	int	nentries;
    160 	int	loaded;
    161 	u64int addr;
    162 	u64int limit;
    163 	Arena *arena;
    164 	int g;
    165 };
    166 
    167 static ISum*
    168 scachelookup(u64int addr)
    169 {
    170 	int i;
    171 	ISum *s;
    172 
    173 	for(i=0; i<icache.nsum; i++){
    174 		s = icache.sum[i];
    175 		if(s->addr <= addr && addr < s->limit){
    176 			if(i > 0){
    177 				memmove(icache.sum+1, icache.sum, i*sizeof icache.sum[0]);
    178 				icache.sum[0] = s;
    179 			}
    180 			return s;
    181 		}
    182 	}
    183 	return nil;
    184 }
    185 
    186 static void
    187 sumclear(ISum *s)
    188 {
    189 	int i;
    190 
    191 	for(i=0; i<s->nentries; i++)
    192 		ihashdelete(icache.shash, &s->entries[i], "scache");
    193 	s->nentries = 0;
    194 	s->loaded = 0;
    195 	s->addr = 0;
    196 	s->limit = 0;
    197 	s->arena = nil;
    198 	s->g = 0;
    199 }
    200 
    201 static ISum*
    202 scacheevict(void)
    203 {
    204 	ISum *s;
    205 	int i;
    206 
    207 	for(i=icache.nsum-1; i>=0; i--){
    208 		s = icache.sum[i];
    209 		if(canqlock(&s->lock)){
    210 			if(i > 0){
    211 				memmove(icache.sum+1, icache.sum, i*sizeof icache.sum[0]);
    212 				icache.sum[0] = s;
    213 			}
    214 			sumclear(s);
    215 			return s;
    216 		}
    217 	}
    218 	return nil;
    219 }
    220 
    221 static void
    222 scachehit(u64int addr)
    223 {
    224 	scachelookup(addr);	/* for move-to-front */
    225 }
    226 
    227 static void
    228 scachesetup(ISum *s, u64int addr)
    229 {
    230 	u64int addr0, limit;
    231 	int g;
    232 
    233 	s->arena = amapitoag(mainindex, addr, &addr0, &limit, &g);
    234 	s->addr = addr0;
    235 	s->limit = limit;
    236 	s->g = g;
    237 }
    238 
    239 static void
    240 scacheload(ISum *s)
    241 {
    242 	int i, n;
    243 
    244 	s->loaded = 1;
    245 	n = asumload(s->arena, s->g, s->entries, ArenaCIGSize);
    246 	/*
    247 	 * n can be less then ArenaCIGSize, either if the clump group
    248 	 * is the last in the arena and is only partially filled, or if there
    249 	 * are corrupt clumps in the group -- those are not returned.
    250 	 */
    251 	for(i=0; i<n; i++){
    252 		s->entries[i].ia.addr += s->addr;
    253 		ihashinsert(icache.shash, &s->entries[i]);
    254 	}
    255 //fprint(2, "%T scacheload %s %d - %d entries\n", s->arena->name, s->g, n);
    256 	addstat(StatScachePrefetch, n);
    257 	s->nentries = n;
    258 }
    259 
    260 static ISum*
    261 scachemiss(u64int addr)
    262 {
    263 	ISum *s;
    264 
    265 	if(!icacheprefetch)
    266 		return nil;
    267 	s = scachelookup(addr);
    268 	if(s == nil){
    269 		/* first time: make an entry in the cache but don't populate it yet */
    270 		s = scacheevict();
    271 		if(s == nil)
    272 			return nil;
    273 		scachesetup(s, addr);
    274 		qunlock(&s->lock);
    275 		return nil;
    276 	}
    277 
    278 	/* second time: load from disk */
    279 	qlock(&s->lock);
    280 	if(s->loaded || !icacheprefetch){
    281 		qunlock(&s->lock);
    282 		return nil;
    283 	}
    284 
    285 	return s;	/* locked */
    286 }
    287 
    288 /*
    289  * Index cache.
    290  */
    291 
    292 void
    293 initicache(u32int mem0)
    294 {
    295 	u32int mem;
    296 	int i, entries, scache;
    297 
    298 	icache.full.l = &icache.lock;
    299 
    300 	mem = mem0;
    301 	entries = mem / (sizeof(IEntry)+sizeof(IEntry*));
    302 	scache = (entries/8) / ArenaCIGSize;
    303 	entries -= entries/8;
    304 	if(scache < 4)
    305 		scache = 4;
    306 	if(scache > 16)
    307 		scache = 16;
    308 	if(entries < 1000)
    309 		entries = 1000;
    310 fprint(2, "icache %,d bytes = %,d entries; %d scache\n", mem0, entries, scache);
    311 
    312 	icache.clean.prev = icache.clean.next = &icache.clean;
    313 	icache.dirty.prev = icache.dirty.next = &icache.dirty;
    314 	icache.free.prev = icache.free.next = (IEntry*)&icache.free;
    315 
    316 	icache.hash = mkihash(entries);
    317 	icache.nentries = entries;
    318 	setstat(StatIcacheSize, entries);
    319 	icache.entries = vtmallocz(entries*sizeof icache.entries[0]);
    320 	icache.maxdirty = entries / 2;
    321 	for(i=0; i<entries; i++)
    322 		pushfirst(&icache.free, &icache.entries[i]);
    323 
    324 	icache.nsum = scache;
    325 	icache.sum = vtmallocz(scache*sizeof icache.sum[0]);
    326 	icache.sum[0] = vtmallocz(scache*sizeof icache.sum[0][0]);
    327 	icache.nsentries = scache * ArenaCIGSize;
    328 	icache.sentries = vtmallocz(scache*ArenaCIGSize*sizeof icache.sentries[0]);
    329 	icache.shash = mkihash(scache*ArenaCIGSize);
    330 	for(i=0; i<scache; i++){
    331 		icache.sum[i] = icache.sum[0] + i;
    332 		icache.sum[i]->entries = icache.sentries + i*ArenaCIGSize;
    333 	}
    334 }
    335 
    336 
    337 static IEntry*
    338 evictlru(void)
    339 {
    340 	IEntry *ie;
    341 
    342 	ie = poplast(&icache.clean);
    343 	if(ie == nil)
    344 		return nil;
    345 	ihashdelete(icache.hash, ie, "evictlru");
    346 	return ie;
    347 }
    348 
    349 static void
    350 icacheinsert(u8int score[VtScoreSize], IAddr *ia, int state)
    351 {
    352 	IEntry *ie;
    353 
    354 	if((ie = poplast(&icache.free)) == nil && (ie = evictlru()) == nil){
    355 		addstat(StatIcacheStall, 1);
    356 		while((ie = poplast(&icache.free)) == nil && (ie = evictlru()) == nil){
    357 			// Could safely return here if state == IEClean.
    358 			// But if state == IEDirty, have to wait to make
    359 			// sure we don't lose an index write.
    360 			// Let's wait all the time.
    361 			flushdcache();
    362 			kickicache();
    363 			rsleep(&icache.full);
    364 		}
    365 		addstat(StatIcacheStall, -1);
    366 	}
    367 
    368 	memmove(ie->score, score, VtScoreSize);
    369 	ie->state = state;
    370 	ie->ia = *ia;
    371 	if(state == IEClean){
    372 		addstat(StatIcachePrefetch, 1);
    373 		pushfirst(&icache.clean, ie);
    374 	}else{
    375 		addstat(StatIcacheWrite, 1);
    376 		assert(state == IEDirty);
    377 		icache.ndirty++;
    378 		setstat(StatIcacheDirty, icache.ndirty);
    379 		delaykickicache();
    380 		pushfirst(&icache.dirty, ie);
    381 	}
    382 	ihashinsert(icache.hash, ie);
    383 }
    384 
    385 int
    386 icachelookup(u8int score[VtScoreSize], int type, IAddr *ia)
    387 {
    388 	IEntry *ie;
    389 
    390 	if(bootstrap)
    391 		return -1;
    392 
    393 	qlock(&icache.lock);
    394 	addstat(StatIcacheLookup, 1);
    395 	if((ie = ihashlookup(icache.hash, score, type)) != nil){
    396 		*ia = ie->ia;
    397 		if(ie->state == IEClean)
    398 			pushfirst(&icache.clean, ie);
    399 		addstat(StatIcacheHit, 1);
    400 		qunlock(&icache.lock);
    401 		return 0;
    402 	}
    403 
    404 	if((ie = ihashlookup(icache.shash, score, type)) != nil){
    405 		*ia = ie->ia;
    406 		icacheinsert(score, &ie->ia, IEClean);
    407 		scachehit(ie->ia.addr);
    408 		addstat(StatScacheHit, 1);
    409 		qunlock(&icache.lock);
    410 		return 0;
    411 	}
    412 	addstat(StatIcacheMiss, 1);
    413 	qunlock(&icache.lock);
    414 
    415 	return -1;
    416 }
    417 
    418 int
    419 insertscore(u8int score[VtScoreSize], IAddr *ia, int state, AState *as)
    420 {
    421 	ISum *toload;
    422 
    423 	if(bootstrap)
    424 		return -1;
    425 
    426 	qlock(&icache.lock);
    427 	icacheinsert(score, ia, state);
    428 	if(state == IEClean)
    429 		toload = scachemiss(ia->addr);
    430 	else{
    431 		assert(state == IEDirty);
    432 		toload = nil;
    433 		if(as == nil)
    434 			fprint(2, "%T insertscore IEDirty without as; called from %#p\n",
    435 				getcallerpc(&score));
    436 		else{
    437 			if(icache.as.aa > as->aa)
    438 				fprint(2, "%T insertscore: aa moving backward: %#llux -> %#llux\n", icache.as.aa, as->aa);
    439 			icache.as = *as;
    440 		}
    441 	}
    442 	qunlock(&icache.lock);
    443 	if(toload){
    444 		scacheload(toload);
    445 		qunlock(&toload->lock);
    446 	}
    447 
    448 	if(icache.ndirty >= icache.maxdirty)
    449 		kickicache();
    450 
    451 	/*
    452 	 * It's okay not to do this under icache.lock.
    453 	 * Calling insertscore only happens when we hold
    454 	 * the lump, meaning any searches for this block
    455 	 * will hit in the lump cache until after we return.
    456 	 */
    457 	if(state == IEDirty)
    458 		markbloomfilter(mainindex->bloom, score);
    459 
    460 	return 0;
    461 }
    462 
    463 int
    464 lookupscore(u8int score[VtScoreSize], int type, IAddr *ia)
    465 {
    466 	int ms, ret;
    467 	IEntry d;
    468 
    469 	if(icachelookup(score, type, ia) >= 0){
    470 		addstat(StatIcacheRead, 1);
    471 		return 0;
    472 	}
    473 
    474 	ms = msec();
    475 	addstat(StatIcacheFill, 1);
    476 	if(loadientry(mainindex, score, type, &d) < 0)
    477 		ret = -1;
    478 	else{
    479 		ret = 0;
    480 		insertscore(score, &d.ia, IEClean, nil);
    481 		*ia = d.ia;
    482 	}
    483 	addstat2(StatIcacheRead, 1, StatIcacheReadTime, msec() - ms);
    484 	return ret;
    485 }
    486 
    487 u32int
    488 hashbits(u8int *sc, int bits)
    489 {
    490 	u32int v;
    491 
    492 	v = (sc[0] << 24) | (sc[1] << 16) | (sc[2] << 8) | sc[3];
    493 	if(bits < 32)
    494 		 v >>= (32 - bits);
    495 	return v;
    496 }
    497 
    498 ulong
    499 icachedirtyfrac(void)
    500 {
    501 	return (vlong)icache.ndirty*IcacheFrac / icache.nentries;
    502 }
    503 
    504 /*
    505  * Return a singly-linked list of dirty index entries.
    506  * with 32-bit hash numbers between lo and hi
    507  * and address < limit.
    508  */
    509 IEntry*
    510 icachedirty(u32int lo, u32int hi, u64int limit)
    511 {
    512 	u32int h;
    513 	IEntry *ie, *dirty;
    514 
    515 	dirty = nil;
    516 	trace(TraceProc, "icachedirty enter");
    517 	qlock(&icache.lock);
    518 	for(ie = icache.dirty.next; ie != &icache.dirty; ie=ie->next){
    519 		if(ie->state == IEDirty && ie->ia.addr <= limit){
    520 			h = hashbits(ie->score, 32);
    521 			if(lo <= h && h <= hi){
    522 				ie->nextdirty = dirty;
    523 				dirty = ie;
    524 			}
    525 		}
    526 	}
    527 	qunlock(&icache.lock);
    528 	trace(TraceProc, "icachedirty exit");
    529 	if(dirty == nil)
    530 		flushdcache();
    531 	return dirty;
    532 }
    533 
    534 AState
    535 icachestate(void)
    536 {
    537 	AState as;
    538 
    539 	qlock(&icache.lock);
    540 	as = icache.as;
    541 	qunlock(&icache.lock);
    542 	return as;
    543 }
    544 
    545 /*
    546  * The singly-linked non-circular list of index entries ie
    547  * has been written to disk.  Move them to the clean list.
    548  */
    549 void
    550 icacheclean(IEntry *ie)
    551 {
    552 	IEntry *next;
    553 
    554 	trace(TraceProc, "icacheclean enter");
    555 	qlock(&icache.lock);
    556 	for(; ie; ie=next){
    557 		assert(ie->state == IEDirty);
    558 		next = ie->nextdirty;
    559 		ie->nextdirty = nil;
    560 		popout(ie); /* from icache.dirty */
    561 		icache.ndirty--;
    562 		ie->state = IEClean;
    563 		pushfirst(&icache.clean, ie);
    564 	}
    565 	setstat(StatIcacheDirty, icache.ndirty);
    566 	rwakeupall(&icache.full);
    567 	qunlock(&icache.lock);
    568 	trace(TraceProc, "icacheclean exit");
    569 }
    570 
    571 void
    572 emptyicache(void)
    573 {
    574 	int i;
    575 	IEntry *ie;
    576 	ISum *s;
    577 
    578 	qlock(&icache.lock);
    579 	while((ie = evictlru()) != nil)
    580 		pushfirst(&icache.free, ie);
    581 	for(i=0; i<icache.nsum; i++){
    582 		s = icache.sum[i];
    583 		qlock(&s->lock);
    584 		sumclear(s);
    585 		qunlock(&s->lock);
    586 	}
    587 	qunlock(&icache.lock);
    588 }