@@ -184,6 +184,14 @@ template <typename T> class NonZeroLengthArray<T, 0> {
184
184
template <typename Config, void (*unmapCallBack)(MemMapT &) = unmap>
185
185
class MapAllocatorCache {
186
186
public:
187
+ typedef enum { COMMITTED = 0 , DECOMMITTED = 1 , NONE } EntryListT;
188
+
189
+ // TODO: Refactor the intrusive list to support non-pointer link type
190
+ typedef struct {
191
+ u16 Head;
192
+ u16 Tail;
193
+ } ListInfo;
194
+
187
195
void getStats (ScopedString *Str) {
188
196
ScopedLock L (Mutex);
189
197
uptr Integral;
@@ -201,13 +209,18 @@ class MapAllocatorCache {
201
209
SuccessfulRetrieves, CallsToRetrieve, Integral, Fractional);
202
210
Str->append (" Cache Entry Info (Most Recent -> Least Recent):\n " );
203
211
204
- for (u32 I = LRUHead; I != CachedBlock::InvalidEntry; I = Entries[I].Next ) {
205
- CachedBlock &Entry = Entries[I];
206
- Str->append (" StartBlockAddress: 0x%zx, EndBlockAddress: 0x%zx, "
207
- " BlockSize: %zu %s\n " ,
208
- Entry.CommitBase , Entry.CommitBase + Entry.CommitSize ,
209
- Entry.CommitSize , Entry.Time == 0 ? " [R]" : " " );
210
- }
212
+ auto printList = [&](EntryListT ListType) REQUIRES (Mutex) {
213
+ for (u32 I = EntryLists[ListType].Head ; I != CachedBlock::InvalidEntry;
214
+ I = Entries[I].Next ) {
215
+ CachedBlock &Entry = Entries[I];
216
+ Str->append (" StartBlockAddress: 0x%zx, EndBlockAddress: 0x%zx, "
217
+ " BlockSize: %zu %s\n " ,
218
+ Entry.CommitBase , Entry.CommitBase + Entry.CommitSize ,
219
+ Entry.CommitSize , Entry.Time == 0 ? " [R]" : " " );
220
+ }
221
+ };
222
+ printList (COMMITTED);
223
+ printList (DECOMMITTED);
211
224
}
212
225
213
226
// Ensure the default maximum specified fits the array.
@@ -231,8 +244,10 @@ class MapAllocatorCache {
231
244
setOption (Option::ReleaseInterval, static_cast <sptr>(ReleaseToOsInterval));
232
245
233
246
// The cache is initially empty
234
- LRUHead = CachedBlock::InvalidEntry;
235
- LRUTail = CachedBlock::InvalidEntry;
247
+ EntryLists[COMMITTED].Head = CachedBlock::InvalidEntry;
248
+ EntryLists[COMMITTED].Tail = CachedBlock::InvalidEntry;
249
+ EntryLists[DECOMMITTED].Head = CachedBlock::InvalidEntry;
250
+ EntryLists[DECOMMITTED].Tail = CachedBlock::InvalidEntry;
236
251
237
252
// Available entries will be retrieved starting from the beginning of the
238
253
// Entries array
@@ -250,7 +265,6 @@ class MapAllocatorCache {
250
265
const s32 Interval = atomic_load_relaxed (&ReleaseToOsIntervalMs);
251
266
u64 Time;
252
267
CachedBlock Entry;
253
-
254
268
Entry.CommitBase = CommitBase;
255
269
Entry.CommitSize = CommitSize;
256
270
Entry.BlockBegin = BlockBegin;
@@ -312,18 +326,27 @@ class MapAllocatorCache {
312
326
Entry = PrevEntry;
313
327
}
314
328
315
- // All excess entries are evicted from the cache
329
+ // All excess entries are evicted from the cache.
330
+ // DECOMMITTED entries, being older than the COMMITTED
331
+ // entries, are evicted first in least recently used (LRU)
332
+ // fashioned followed by the COMMITTED entries
316
333
while (needToEvict ()) {
334
+ EntryListT EvictionListType;
335
+ if (EntryLists[DECOMMITTED].Tail == CachedBlock::InvalidEntry)
336
+ EvictionListType = COMMITTED;
337
+ else
338
+ EvictionListType = DECOMMITTED;
317
339
// Save MemMaps of evicted entries to perform unmap outside of lock
318
- EvictionMemMaps.push_back (Entries[LRUTail].MemMap );
319
- remove (LRUTail);
340
+ EvictionMemMaps.push_back (
341
+ Entries[EntryLists[EvictionListType].Tail ].MemMap );
342
+ remove (EntryLists[EvictionListType].Tail , EvictionListType);
320
343
}
321
344
322
- insert (Entry);
345
+ insert (Entry, (Entry. Time == 0 ) ? DECOMMITTED : COMMITTED );
323
346
324
347
if (OldestTime == 0 )
325
348
OldestTime = Entry.Time ;
326
- } while (0 );
349
+ } while (0 ); // ScopedLock L(Mutex);
327
350
328
351
for (MemMapT &EvictMemMap : EvictionMemMaps)
329
352
unmapCallBack (EvictMemMap);
@@ -340,17 +363,14 @@ class MapAllocatorCache {
340
363
// 10% of the requested size proved to be the optimal choice for
341
364
// retrieving cached blocks after testing several options.
342
365
constexpr u32 FragmentedBytesDivisor = 10 ;
343
- bool Found = false ;
344
366
CachedBlock Entry;
367
+ uptr OptimalFitIndex = CachedBlock::InvalidEntry;
368
+ uptr MinDiff = UINTPTR_MAX;
369
+ EntryListT OptimalFitListType = NONE;
345
370
EntryHeaderPos = 0 ;
346
- {
347
- ScopedLock L (Mutex);
348
- CallsToRetrieve++;
349
- if (EntriesCount == 0 )
350
- return {};
351
- u32 OptimalFitIndex = 0 ;
352
- uptr MinDiff = UINTPTR_MAX;
353
- for (u32 I = LRUHead; I != CachedBlock::InvalidEntry;
371
+
372
+ auto FindAvailableEntry = [&](EntryListT ListType) REQUIRES (Mutex) {
373
+ for (uptr I = EntryLists[ListType].Head ; I != CachedBlock::InvalidEntry;
354
374
I = Entries[I].Next ) {
355
375
const uptr CommitBase = Entries[I].CommitBase ;
356
376
const uptr CommitSize = Entries[I].CommitSize ;
@@ -360,34 +380,48 @@ class MapAllocatorCache {
360
380
if (HeaderPos > CommitBase + CommitSize)
361
381
continue ;
362
382
if (HeaderPos < CommitBase ||
363
- AllocPos > CommitBase + PageSize * MaxUnusedCachePages) {
383
+ AllocPos > CommitBase + PageSize * MaxUnusedCachePages)
364
384
continue ;
365
- }
366
- Found = true ;
385
+
367
386
const uptr Diff = HeaderPos - CommitBase;
368
- // immediately use a cached block if it's size is close enough to the
369
- // requested size.
387
+ // immediately use a cached block if it's size is close enough to
388
+ // the requested size.
370
389
const uptr MaxAllowedFragmentedBytes =
371
390
(CommitBase + CommitSize - HeaderPos) / FragmentedBytesDivisor;
372
391
if (Diff <= MaxAllowedFragmentedBytes) {
373
392
OptimalFitIndex = I;
374
393
EntryHeaderPos = HeaderPos;
375
- break ;
394
+ OptimalFitListType = ListType;
395
+ return true ;
376
396
}
397
+
377
398
// keep track of the smallest cached block
378
399
// that is greater than (AllocSize + HeaderSize)
379
400
if (Diff > MinDiff)
380
401
continue ;
381
402
OptimalFitIndex = I;
382
403
MinDiff = Diff;
404
+ OptimalFitListType = ListType;
383
405
EntryHeaderPos = HeaderPos;
384
406
}
385
- if (Found) {
386
- Entry = Entries[OptimalFitIndex];
387
- remove (OptimalFitIndex);
388
- SuccessfulRetrieves++;
389
- }
390
- }
407
+ return (OptimalFitIndex != CachedBlock::InvalidEntry);
408
+ };
409
+
410
+ {
411
+ ScopedLock L (Mutex);
412
+ CallsToRetrieve++;
413
+ if (EntriesCount == 0 )
414
+ return {};
415
+
416
+ // Prioritize valid fit from COMMITTED entries over
417
+ // optimal fit from DECOMMITTED entries
418
+ if (!FindAvailableEntry (COMMITTED) && !FindAvailableEntry (DECOMMITTED))
419
+ return {};
420
+
421
+ Entry = Entries[OptimalFitIndex];
422
+ remove (OptimalFitIndex, OptimalFitListType);
423
+ SuccessfulRetrieves++;
424
+ } // ScopedLock L(Mutex);
391
425
392
426
return Entry;
393
427
}
@@ -432,10 +466,15 @@ class MapAllocatorCache {
432
466
Quarantine[I].invalidate ();
433
467
}
434
468
}
435
- for (u32 I = LRUHead; I != CachedBlock::InvalidEntry; I = Entries[I].Next ) {
436
- Entries[I].MemMap .setMemoryPermission (Entries[I].CommitBase ,
437
- Entries[I].CommitSize , 0 );
438
- }
469
+ auto disableLists = [&](EntryListT EntryList) REQUIRES (Mutex) {
470
+ for (u32 I = EntryLists[EntryList].Head ; I != CachedBlock::InvalidEntry;
471
+ I = Entries[I].Next ) {
472
+ Entries[I].MemMap .setMemoryPermission (Entries[I].CommitBase ,
473
+ Entries[I].CommitSize , 0 );
474
+ }
475
+ };
476
+ disableLists (COMMITTED);
477
+ disableLists (DECOMMITTED);
439
478
QuarantinePos = -1U ;
440
479
}
441
480
@@ -450,7 +489,7 @@ class MapAllocatorCache {
450
489
return (EntriesCount >= atomic_load_relaxed (&MaxEntriesCount));
451
490
}
452
491
453
- void insert (const CachedBlock &Entry) REQUIRES(Mutex) {
492
+ void insert (const CachedBlock &Entry, EntryListT ListType ) REQUIRES(Mutex) {
454
493
DCHECK_LT (EntriesCount, atomic_load_relaxed (&MaxEntriesCount));
455
494
456
495
// Cache should be populated with valid entries when not empty
@@ -459,66 +498,86 @@ class MapAllocatorCache {
459
498
u32 FreeIndex = AvailableHead;
460
499
AvailableHead = Entries[AvailableHead].Next ;
461
500
462
- if (EntriesCount == 0 ) {
463
- LRUTail = static_cast <u16>(FreeIndex);
464
- } else {
465
- // Check list order
466
- if (EntriesCount > 1 )
467
- DCHECK_GE (Entries[LRUHead].Time , Entries[Entries[LRUHead].Next ].Time );
468
- Entries[LRUHead].Prev = static_cast <u16>(FreeIndex);
469
- }
470
-
471
501
Entries[FreeIndex] = Entry;
472
- Entries[FreeIndex].Next = LRUHead;
473
- Entries[FreeIndex].Prev = CachedBlock::InvalidEntry;
474
- LRUHead = static_cast <u16>(FreeIndex);
502
+ pushFront (FreeIndex, ListType);
475
503
EntriesCount++;
476
504
505
+ if (Entries[EntryLists[ListType].Head ].Next != CachedBlock::InvalidEntry) {
506
+ DCHECK_GE (Entries[EntryLists[ListType].Head ].Time ,
507
+ Entries[Entries[EntryLists[ListType].Head ].Next ].Time );
508
+ }
477
509
// Availability stack should not have available entries when all entries
478
510
// are in use
479
511
if (EntriesCount == Config::getEntriesArraySize ())
480
512
DCHECK_EQ (AvailableHead, CachedBlock::InvalidEntry);
481
513
}
482
514
483
- void remove (uptr I) REQUIRES(Mutex) {
484
- DCHECK (Entries[I].isValid ());
485
-
486
- Entries[I].invalidate ();
487
-
488
- if (I == LRUHead)
489
- LRUHead = Entries[I].Next ;
515
+ // Joins the entries adjacent to Entries[I], effectively
516
+ // unlinking Entries[I] from the list
517
+ void unlink (uptr I, EntryListT ListType) REQUIRES(Mutex) {
518
+ if (I == EntryLists[ListType].Head )
519
+ EntryLists[ListType].Head = Entries[I].Next ;
490
520
else
491
521
Entries[Entries[I].Prev ].Next = Entries[I].Next ;
492
522
493
- if (I == LRUTail )
494
- LRUTail = Entries[I].Prev ;
523
+ if (I == EntryLists[ListType]. Tail )
524
+ EntryLists[ListType]. Tail = Entries[I].Prev ;
495
525
else
496
526
Entries[Entries[I].Next ].Prev = Entries[I].Prev ;
527
+ }
528
+
529
+ // Invalidates Entries[I], removes Entries[I] from list, and pushes
530
+ // Entries[I] onto the stack of available entries
531
+ void remove (uptr I, EntryListT ListType) REQUIRES(Mutex) {
532
+ DCHECK (Entries[I].isValid ());
533
+
534
+ Entries[I].invalidate ();
497
535
536
+ unlink (I, ListType);
498
537
Entries[I].Next = AvailableHead;
499
538
AvailableHead = static_cast <u16>(I);
500
539
EntriesCount--;
501
540
502
541
// Cache should not have valid entries when not empty
503
542
if (EntriesCount == 0 ) {
504
- DCHECK_EQ (LRUHead, CachedBlock::InvalidEntry);
505
- DCHECK_EQ (LRUTail, CachedBlock::InvalidEntry);
543
+ DCHECK_EQ (EntryLists[COMMITTED].Head , CachedBlock::InvalidEntry);
544
+ DCHECK_EQ (EntryLists[COMMITTED].Tail , CachedBlock::InvalidEntry);
545
+ DCHECK_EQ (EntryLists[DECOMMITTED].Head , CachedBlock::InvalidEntry);
546
+ DCHECK_EQ (EntryLists[DECOMMITTED].Tail , CachedBlock::InvalidEntry);
506
547
}
507
548
}
508
549
550
+ inline void pushFront (uptr I, EntryListT ListType) REQUIRES(Mutex) {
551
+ if (EntryLists[ListType].Tail == CachedBlock::InvalidEntry)
552
+ EntryLists[ListType].Tail = static_cast <u16>(I);
553
+ else
554
+ Entries[EntryLists[ListType].Head ].Prev = static_cast <u16>(I);
555
+
556
+ Entries[I].Next = EntryLists[ListType].Head ;
557
+ Entries[I].Prev = CachedBlock::InvalidEntry;
558
+ EntryLists[ListType].Head = static_cast <u16>(I);
559
+ }
560
+
509
561
void empty () {
510
562
MemMapT MapInfo[Config::getEntriesArraySize ()];
511
563
uptr N = 0 ;
512
564
{
513
565
ScopedLock L (Mutex);
514
- for (uptr I = 0 ; I < Config::getEntriesArraySize (); I++) {
515
- if (!Entries[I].isValid ())
516
- continue ;
517
- MapInfo[N] = Entries[I].MemMap ;
518
- remove (I);
519
- N++;
520
- }
566
+ auto emptyList = [&](EntryListT ListType) REQUIRES (Mutex) {
567
+ for (uptr I = EntryLists[ListType].Head ;
568
+ I != CachedBlock::InvalidEntry;) {
569
+ uptr ToRemove = I;
570
+ I = Entries[I].Next ;
571
+ MapInfo[N] = Entries[ToRemove].MemMap ;
572
+ remove (ToRemove, ListType);
573
+ N++;
574
+ }
575
+ };
576
+ emptyList (COMMITTED);
577
+ emptyList (DECOMMITTED);
521
578
EntriesCount = 0 ;
579
+ for (uptr I = 0 ; I < Config::getEntriesArraySize (); I++)
580
+ DCHECK (!Entries[I].isValid ());
522
581
}
523
582
for (uptr I = 0 ; I < N; I++) {
524
583
MemMapT &MemMap = MapInfo[I];
@@ -545,8 +604,14 @@ class MapAllocatorCache {
545
604
OldestTime = 0 ;
546
605
for (uptr I = 0 ; I < Config::getQuarantineSize (); I++)
547
606
releaseIfOlderThan (Quarantine[I], Time);
548
- for (uptr I = 0 ; I < Config::getEntriesArraySize (); I++)
607
+ for (u16 I = EntryLists[COMMITTED].Head ; I != CachedBlock::InvalidEntry;
608
+ I = Entries[I].Next ) {
609
+ if (Entries[I].Time && Entries[I].Time <= Time) {
610
+ unlink (I, COMMITTED);
611
+ pushFront (I, DECOMMITTED);
612
+ }
549
613
releaseIfOlderThan (Entries[I], Time);
614
+ }
550
615
}
551
616
552
617
HybridMutex Mutex;
@@ -563,10 +628,12 @@ class MapAllocatorCache {
563
628
NonZeroLengthArray<CachedBlock, Config::getQuarantineSize()>
564
629
Quarantine GUARDED_BY (Mutex) = {};
565
630
566
- // The LRUHead of the cache is the most recently used cache entry
567
- u16 LRUHead GUARDED_BY (Mutex) = 0;
568
- // The LRUTail of the cache is the least recently used cache entry
569
- u16 LRUTail GUARDED_BY (Mutex) = 0;
631
+ // EntryLists stores the head and tail indices of all
632
+ // lists being used to store valid cache entries.
633
+ // Currently there are lists storing COMMITTED and DECOMMITTED entries.
634
+ // COMMITTED entries have memory chunks that have not been released to the OS
635
+ // DECOMMITTED entries have memory chunks that have been released to the OS
636
+ ListInfo EntryLists[2 ] GUARDED_BY(Mutex) = {};
570
637
// The AvailableHead is the top of the stack of available entries
571
638
u16 AvailableHead GUARDED_BY (Mutex) = 0;
572
639
};
@@ -706,6 +773,7 @@ MapAllocator<Config>::tryAllocateFromCache(const Options &Options, uptr Size,
706
773
}
707
774
return Ptr ;
708
775
}
776
+
709
777
// As with the Primary, the size passed to this function includes any desired
710
778
// alignment, so that the frontend can align the user allocation. The hint
711
779
// parameter allows us to unmap spurious memory when dealing with larger
0 commit comments