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 }