Skip to content

Commit 54605cc

Browse files
cferris1000aokblast
authored andcommitted
[scudo] Secondary release to OS uses LRU to scan. (llvm#163691)
Before this change, the code would scan the entire set of cached entries to find ones to be released. Now, it uses the LRUEntries list to iterate over the live cached entries. In addition, remove the OldestTime variable and replace it with OldestPresentEntry which will always be the oldest entry in the LRU that has Time non-zero.
1 parent d21e5cf commit 54605cc

File tree

2 files changed

+127
-27
lines changed

2 files changed

+127
-27
lines changed

compiler-rt/lib/scudo/standalone/secondary.h

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ class MapAllocatorCache {
249249

250250
LRUEntries.clear();
251251
LRUEntries.init(Entries, sizeof(Entries));
252+
OldestPresentEntry = nullptr;
252253

253254
AvailEntries.clear();
254255
AvailEntries.init(Entries, sizeof(Entries));
@@ -322,8 +323,6 @@ class MapAllocatorCache {
322323
}
323324
CachedBlock PrevEntry = Quarantine[QuarantinePos];
324325
Quarantine[QuarantinePos] = Entry;
325-
if (OldestTime == 0)
326-
OldestTime = Entry.Time;
327326
Entry = PrevEntry;
328327
}
329328

@@ -339,9 +338,6 @@ class MapAllocatorCache {
339338
}
340339

341340
insert(Entry);
342-
343-
if (OldestTime == 0)
344-
OldestTime = Entry.Time;
345341
} while (0);
346342

347343
for (MemMapT &EvictMemMap : EvictionMemMaps)
@@ -355,7 +351,6 @@ class MapAllocatorCache {
355351
SCUDO_SCOPED_TRACE(
356352
GetSecondaryReleaseToOSTraceName(ReleaseToOS::Normal));
357353

358-
// TODO: Add ReleaseToOS logic to LRU algorithm
359354
releaseOlderThan(Time - static_cast<u64>(Interval) * 1000000);
360355
Mutex.unlock();
361356
} else
@@ -535,17 +530,28 @@ class MapAllocatorCache {
535530

536531
void unmapTestOnly() { empty(); }
537532

533+
void releaseOlderThanTestOnly(u64 ReleaseTime) {
534+
ScopedLock L(Mutex);
535+
releaseOlderThan(ReleaseTime);
536+
}
537+
538538
private:
539539
void insert(const CachedBlock &Entry) REQUIRES(Mutex) {
540540
CachedBlock *AvailEntry = AvailEntries.front();
541541
AvailEntries.pop_front();
542542

543543
*AvailEntry = Entry;
544544
LRUEntries.push_front(AvailEntry);
545+
if (OldestPresentEntry == nullptr && AvailEntry->Time != 0)
546+
OldestPresentEntry = AvailEntry;
545547
}
546548

547549
void remove(CachedBlock *Entry) REQUIRES(Mutex) {
548550
DCHECK(Entry->isValid());
551+
if (OldestPresentEntry == Entry) {
552+
OldestPresentEntry = LRUEntries.getPrev(Entry);
553+
DCHECK(OldestPresentEntry == nullptr || OldestPresentEntry->Time != 0);
554+
}
549555
LRUEntries.remove(Entry);
550556
Entry->invalidate();
551557
AvailEntries.push_front(Entry);
@@ -560,43 +566,50 @@ class MapAllocatorCache {
560566
for (CachedBlock &Entry : LRUEntries)
561567
MapInfo[N++] = Entry.MemMap;
562568
LRUEntries.clear();
569+
OldestPresentEntry = nullptr;
563570
}
564571
for (uptr I = 0; I < N; I++) {
565572
MemMapT &MemMap = MapInfo[I];
566573
unmapCallBack(MemMap);
567574
}
568575
}
569576

570-
void releaseIfOlderThan(CachedBlock &Entry, u64 Time) REQUIRES(Mutex) {
571-
if (!Entry.isValid() || !Entry.Time)
572-
return;
573-
if (Entry.Time > Time) {
574-
if (OldestTime == 0 || Entry.Time < OldestTime)
575-
OldestTime = Entry.Time;
576-
return;
577+
void releaseOlderThan(u64 ReleaseTime) REQUIRES(Mutex) {
578+
SCUDO_SCOPED_TRACE(GetSecondaryReleaseOlderThanTraceName());
579+
580+
if (!Config::getQuarantineDisabled()) {
581+
for (uptr I = 0; I < Config::getQuarantineSize(); I++) {
582+
auto &Entry = Quarantine[I];
583+
if (!Entry.isValid() || Entry.Time == 0 || Entry.Time > ReleaseTime)
584+
continue;
585+
Entry.MemMap.releaseAndZeroPagesToOS(Entry.CommitBase,
586+
Entry.CommitSize);
587+
Entry.Time = 0;
588+
}
577589
}
578-
Entry.MemMap.releaseAndZeroPagesToOS(Entry.CommitBase, Entry.CommitSize);
579-
Entry.Time = 0;
580-
}
581590

582-
void releaseOlderThan(u64 Time) REQUIRES(Mutex) {
583-
SCUDO_SCOPED_TRACE(GetSecondaryReleaseOlderThanTraceName());
591+
for (CachedBlock *Entry = OldestPresentEntry; Entry != nullptr;
592+
Entry = LRUEntries.getPrev(Entry)) {
593+
DCHECK(Entry->isValid());
594+
DCHECK(Entry->Time != 0);
595+
596+
if (Entry->Time > ReleaseTime) {
597+
// All entries are newer than this, so no need to keep scanning.
598+
OldestPresentEntry = Entry;
599+
return;
600+
}
584601

585-
if (!LRUEntries.size() || OldestTime == 0 || OldestTime > Time)
586-
return;
587-
OldestTime = 0;
588-
if (!Config::getQuarantineDisabled())
589-
for (uptr I = 0; I < Config::getQuarantineSize(); I++)
590-
releaseIfOlderThan(Quarantine[I], Time);
591-
for (uptr I = 0; I < Config::getEntriesArraySize(); I++)
592-
releaseIfOlderThan(Entries[I], Time);
602+
Entry->MemMap.releaseAndZeroPagesToOS(Entry->CommitBase,
603+
Entry->CommitSize);
604+
Entry->Time = 0;
605+
}
606+
OldestPresentEntry = nullptr;
593607
}
594608

595609
HybridMutex Mutex;
596610
u32 QuarantinePos GUARDED_BY(Mutex) = 0;
597611
atomic_u32 MaxEntriesCount = {};
598612
atomic_uptr MaxEntrySize = {};
599-
u64 OldestTime GUARDED_BY(Mutex) = 0;
600613
atomic_s32 ReleaseToOsIntervalMs = {};
601614
u32 CallsToRetrieve GUARDED_BY(Mutex) = 0;
602615
u32 SuccessfulRetrieves GUARDED_BY(Mutex) = 0;
@@ -606,6 +619,8 @@ class MapAllocatorCache {
606619
NonZeroLengthArray<CachedBlock, Config::getQuarantineSize()>
607620
Quarantine GUARDED_BY(Mutex) = {};
608621

622+
// The oldest entry in the LRUEntries that has Time non-zero.
623+
CachedBlock *OldestPresentEntry GUARDED_BY(Mutex) = nullptr;
609624
// Cached blocks stored in LRU order
610625
DoublyLinkedList<CachedBlock> LRUEntries GUARDED_BY(Mutex);
611626
// The unused Entries

compiler-rt/lib/scudo/standalone/tests/secondary_test.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,11 @@ template <class Config> struct CacheInfoType {
403403
MemMap.getBase(), MemMap);
404404
}
405405
}
406+
407+
void storeMemMap(scudo::MemMapT &MemMap) {
408+
Cache->store(Options, MemMap.getBase(), MemMap.getCapacity(),
409+
MemMap.getBase(), MemMap);
410+
}
406411
};
407412

408413
TEST(ScudoSecondaryTest, AllocatorCacheEntryOrder) {
@@ -503,3 +508,83 @@ TEST(ScudoSecondaryTest, AllocatorCacheOptions) {
503508
Info.Cache->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
504509
EXPECT_TRUE(Info.Cache->canCache(1UL << 16));
505510
}
511+
512+
TEST(ScudoSecondaryTest, ReleaseOlderThanAllEntries) {
513+
CacheInfoType<TestCacheConfig> Info;
514+
using CacheConfig = CacheInfoType<TestCacheConfig>::CacheConfig;
515+
516+
Info.Cache->releaseOlderThanTestOnly(UINT64_MAX);
517+
518+
Info.fillCacheWithSameSizeBlocks(CacheConfig::getDefaultMaxEntriesCount(),
519+
1024);
520+
for (size_t I = 0; I < Info.MemMaps.size(); I++) {
521+
// Set the first u32 value to a non-zero value.
522+
*reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()) = 10;
523+
}
524+
525+
Info.Cache->releaseOlderThanTestOnly(UINT64_MAX);
526+
527+
EXPECT_EQ(Info.MemMaps.size(), CacheConfig::getDefaultMaxEntriesCount());
528+
for (size_t I = 0; I < Info.MemMaps.size(); I++) {
529+
// All released maps will now be zero.
530+
EXPECT_EQ(*reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()), 0U);
531+
}
532+
}
533+
534+
// This test assumes that the timestamp comes from getMonotonicFast.
535+
TEST(ScudoSecondaryTest, ReleaseOlderThanGroups) {
536+
CacheInfoType<TestCacheConfig> Info;
537+
538+
// Disable the release interval so we can do tests the releaseOlderThan
539+
// function.
540+
Info.Cache->setOption(scudo::Option::ReleaseInterval, -1);
541+
542+
// Create all of the maps we are going to use.
543+
for (size_t I = 0; I < 6; I++) {
544+
Info.MemMaps.emplace_back(Info.allocate(1024));
545+
// Set the first u32 value to a non-zero value.
546+
*reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()) = 10;
547+
}
548+
549+
// Create three groups of entries at three different intervals.
550+
Info.storeMemMap(Info.MemMaps[0]);
551+
Info.storeMemMap(Info.MemMaps[1]);
552+
scudo::u64 FirstTime = scudo::getMonotonicTimeFast();
553+
554+
// Need to make sure the next set of entries are stamped with a newer time.
555+
while (scudo::getMonotonicTimeFast() <= FirstTime)
556+
;
557+
558+
Info.storeMemMap(Info.MemMaps[2]);
559+
Info.storeMemMap(Info.MemMaps[3]);
560+
scudo::u64 SecondTime = scudo::getMonotonicTimeFast();
561+
562+
// Need to make sure the next set of entries are stamped with a newer time.
563+
while (scudo::getMonotonicTimeFast() <= SecondTime)
564+
;
565+
566+
Info.storeMemMap(Info.MemMaps[4]);
567+
Info.storeMemMap(Info.MemMaps[5]);
568+
scudo::u64 ThirdTime = scudo::getMonotonicTimeFast();
569+
570+
Info.Cache->releaseOlderThanTestOnly(FirstTime);
571+
for (size_t I = 0; I < 2; I++) {
572+
EXPECT_EQ(*reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()), 0U);
573+
}
574+
for (size_t I = 2; I < 6; I++) {
575+
EXPECT_EQ(*reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()), 10U);
576+
}
577+
578+
Info.Cache->releaseOlderThanTestOnly(SecondTime);
579+
for (size_t I = 0; I < 4; I++) {
580+
EXPECT_EQ(*reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()), 0U);
581+
}
582+
for (size_t I = 4; I < 6; I++) {
583+
EXPECT_EQ(*reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()), 10U);
584+
}
585+
586+
Info.Cache->releaseOlderThanTestOnly(ThirdTime);
587+
for (size_t I = 0; I < 6; I++) {
588+
EXPECT_EQ(*reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()), 0U);
589+
}
590+
}

0 commit comments

Comments
 (0)