Skip to content

Commit 99e8f2c

Browse files
committed
EGL Multifile Blobcache: Fix applyLRU
This CL contains updates our cache eviction to be an actual LRU instead of random. * Entries are now tracked in a sorted map such that old entries are accessed first in applyLRU. * Access and update times are tracked with nanosecond accuracy. Additional tests: * CacheEvictionIsLRU Based on work by: Igor Nazarov <i.nazarov@samsung.com> Test: libEGL_test, EGL_test, ANGLE trace tests, apps Bug: b/351867582, b/380483358 Flag: com.android.graphics.egl.flags.multifile_blobcache_advanced_usage Change-Id: I0b6f407b6d5a29f9487f7d7604d723e701c6f6d5
1 parent 6aebcf2 commit 99e8f2c

3 files changed

Lines changed: 257 additions & 14 deletions

File tree

opengl/libs/EGL/MultifileBlobCache.cpp

Lines changed: 105 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ using namespace std::literals;
4747
constexpr uint32_t kMultifileMagic = 'MFB$';
4848
constexpr uint32_t kCrcPlaceholder = 0;
4949

50+
// When removing files, what fraction of the overall limit should be reached when removing files
51+
// A divisor of two will decrease the cache to 50%, four to 25% and so on
52+
// We use the same limit to manage size and entry count
53+
constexpr uint32_t kCacheLimitDivisor = 2;
54+
5055
namespace {
5156

5257
// Helper function to close entries or free them
@@ -76,6 +81,7 @@ MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, s
7681
mMaxTotalEntries(maxTotalEntries),
7782
mTotalCacheSize(0),
7883
mTotalCacheEntries(0),
84+
mTotalCacheSizeDivisor(kCacheLimitDivisor),
7985
mHotCacheLimit(0),
8086
mHotCacheSize(0),
8187
mWorkerThreadIdle(true) {
@@ -247,7 +253,8 @@ MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, s
247253
ALOGV("INIT: Entry %u is good, tracking it now.", entryHash);
248254

249255
// Track details for rapid lookup later and update total size
250-
trackEntry(entryHash, header.valueSize, fileSize, st.st_atime);
256+
// Note access time is a full timespec instead of just seconds
257+
trackEntry(entryHash, header.valueSize, fileSize, st.st_atim);
251258

252259
// Preload the entry for fast retrieval
253260
if ((mHotCacheSize + fileSize) < mHotCacheLimit) {
@@ -357,7 +364,11 @@ void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const voi
357364
std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
358365

359366
// Track the size and access time for quick recall and update the overall cache size
360-
trackEntry(entryHash, valueSize, fileSize, time(0));
367+
struct timespec time = {0, 0};
368+
if (flags::multifile_blobcache_advanced_usage()) {
369+
clock_gettime(CLOCK_REALTIME, &time);
370+
}
371+
trackEntry(entryHash, valueSize, fileSize, time);
361372

362373
// Keep the entry in hot cache for quick retrieval
363374
ALOGV("SET: Adding %u to hot cache.", entryHash);
@@ -439,6 +450,14 @@ EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize
439450
if (mHotCache.find(entryHash) != mHotCache.end()) {
440451
ALOGV("GET: HotCache HIT for entry %u", entryHash);
441452
cacheEntry = mHotCache[entryHash].entryBuffer;
453+
454+
if (flags::multifile_blobcache_advanced_usage()) {
455+
// Update last access time on disk
456+
struct timespec times[2];
457+
times[0].tv_nsec = UTIME_NOW;
458+
times[1].tv_nsec = UTIME_OMIT;
459+
utimensat(0, fullPath.c_str(), times, 0);
460+
}
442461
} else {
443462
ALOGV("GET: HotCache MISS for entry: %u", entryHash);
444463

@@ -467,6 +486,14 @@ EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize
467486
cacheEntry =
468487
reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
469488

489+
if (flags::multifile_blobcache_advanced_usage()) {
490+
// Update last access time and omit last modify time
491+
struct timespec times[2];
492+
times[0].tv_nsec = UTIME_NOW;
493+
times[1].tv_nsec = UTIME_OMIT;
494+
futimens(fd, times);
495+
}
496+
470497
// We can close the file now and the mmap will remain
471498
close(fd);
472499

@@ -503,6 +530,13 @@ EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize
503530
return 0;
504531
}
505532

533+
if (flags::multifile_blobcache_advanced_usage()) {
534+
// Update the entry time for this hash, so it reflects LRU
535+
struct timespec time;
536+
clock_gettime(CLOCK_REALTIME, &time);
537+
updateEntryTime(entryHash, time);
538+
}
539+
506540
// Remaining entry following the key is the value
507541
uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader));
508542
memcpy(value, cachedValue, cachedValueSize);
@@ -638,9 +672,20 @@ bool MultifileBlobCache::checkStatus(const std::string& baseDir) {
638672
}
639673

640674
void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
641-
time_t accessTime) {
675+
const timespec& accessTime) {
676+
#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
677+
// When we add this entry to the map, it is sorted by accessTime
678+
MultifileEntryStatsMapIter entryStatsIter =
679+
mEntryStats.emplace(std::piecewise_construct, std::forward_as_tuple(accessTime),
680+
std::forward_as_tuple(entryHash, valueSize, fileSize));
681+
682+
// Track all entries with quick access to its stats
683+
mEntries.emplace(entryHash, entryStatsIter);
684+
#else
685+
(void)accessTime;
642686
mEntries.insert(entryHash);
643-
mEntryStats[entryHash] = {valueSize, fileSize, accessTime};
687+
mEntryStats[entryHash] = {entryHash, valueSize, fileSize};
688+
#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
644689

645690
increaseTotalCacheSize(fileSize);
646691
}
@@ -651,12 +696,18 @@ bool MultifileBlobCache::removeEntry(uint32_t entryHash) {
651696
return false;
652697
}
653698

699+
#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
700+
MultifileEntryStatsMapIter entryStatsIter = entryIter->second;
701+
MultifileEntryStats entryStats = entryStatsIter->second;
702+
decreaseTotalCacheSize(entryStats.fileSize);
703+
#else
654704
auto entryStatsIter = mEntryStats.find(entryHash);
655705
if (entryStatsIter == mEntryStats.end()) {
656706
ALOGE("Failed to remove entryHash (%u) from mEntryStats", entryHash);
657707
return false;
658708
}
659709
decreaseTotalCacheSize(entryStatsIter->second.fileSize);
710+
#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
660711

661712
mEntryStats.erase(entryStatsIter);
662713
mEntries.erase(entryIter);
@@ -669,7 +720,40 @@ bool MultifileBlobCache::contains(uint32_t hashEntry) const {
669720
}
670721

671722
MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) {
723+
#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
724+
auto entryIter = mEntries.find(entryHash);
725+
if (entryIter == mEntries.end()) {
726+
return {};
727+
}
728+
729+
MultifileEntryStatsMapIter entryStatsIter = entryIter->second;
730+
MultifileEntryStats entryStats = entryStatsIter->second;
731+
return entryStats;
732+
#else
672733
return mEntryStats[entryHash];
734+
#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
735+
}
736+
737+
void MultifileBlobCache::updateEntryTime(uint32_t entryHash, const timespec& newTime) {
738+
#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
739+
// This function updates the ordering of the map by removing the old iterators
740+
// and re-adding them. If should be perforant as it does not perform a full re-sort.
741+
// First, pull out the old entryStats
742+
auto entryIter = mEntries.find(entryHash);
743+
MultifileEntryStatsMapIter entryStatsIter = entryIter->second;
744+
MultifileEntryStats entryStats = std::move(entryStatsIter->second);
745+
746+
// Remove the old iterators
747+
mEntryStats.erase(entryStatsIter);
748+
mEntries.erase(entryIter);
749+
750+
// Insert the new with updated time
751+
entryStatsIter = mEntryStats.emplace(std::make_pair(newTime, std::move(entryStats)));
752+
mEntries.emplace(entryHash, entryStatsIter);
753+
#else
754+
(void)entryHash;
755+
(void)newTime;
756+
#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
673757
}
674758

675759
void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
@@ -751,8 +835,13 @@ bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) {
751835
bool MultifileBlobCache::applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit) {
752836
// Walk through our map of sorted last access times and remove files until under the limit
753837
for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) {
838+
#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
839+
const MultifileEntryStats& entryStats = cacheEntryIter->second;
840+
uint32_t entryHash = entryStats.entryHash;
841+
#else
754842
uint32_t entryHash = cacheEntryIter->first;
755843
const MultifileEntryStats& entryStats = cacheEntryIter->second;
844+
#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
756845

757846
ALOGV("LRU: Removing entryHash %u", entryHash);
758847

@@ -823,20 +912,17 @@ bool MultifileBlobCache::clearCache() {
823912
return true;
824913
}
825914

826-
// When removing files, what fraction of the overall limit should be reached when removing files
827-
// A divisor of two will decrease the cache to 50%, four to 25% and so on
828-
// We use the same limit to manage size and entry count
829-
constexpr uint32_t kCacheLimitDivisor = 2;
830-
831915
// Calculate the cache size and remove old entries until under the limit
832916
void MultifileBlobCache::trimCache() {
833917
// Wait for all deferred writes to complete
834918
ALOGV("TRIM: Waiting for work to complete.");
835919
waitForWorkComplete();
836920

837921
ALOGV("TRIM: Reducing multifile cache size to %zu, entries %zu",
838-
mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor);
839-
if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor)) {
922+
mMaxTotalSize / mTotalCacheSizeDivisor, mMaxTotalEntries / mTotalCacheSizeDivisor);
923+
924+
if (!applyLRU(mMaxTotalSize / mTotalCacheSizeDivisor,
925+
mMaxTotalEntries / mTotalCacheSizeDivisor)) {
840926
ALOGE("Error when clearing multifile shader cache");
841927
return;
842928
}
@@ -878,6 +964,14 @@ void MultifileBlobCache::processTask(DeferredTask& task) {
878964
return;
879965
}
880966

967+
if (flags::multifile_blobcache_advanced_usage()) {
968+
// Update last access time and last modify time
969+
struct timespec times[2];
970+
times[0].tv_nsec = UTIME_NOW;
971+
times[1].tv_nsec = UTIME_NOW;
972+
futimens(fd, times);
973+
}
974+
881975
ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str());
882976
close(fd);
883977

opengl/libs/EGL/MultifileBlobCache.h

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ struct MultifileHeader {
4949
};
5050

5151
struct MultifileEntryStats {
52+
uint32_t entryHash;
5253
EGLsizeiANDROID valueSize;
5354
size_t fileSize;
54-
time_t accessTime;
5555
};
5656

5757
struct MultifileStatus {
@@ -104,6 +104,26 @@ class DeferredTask {
104104
size_t mBufferSize;
105105
};
106106

107+
#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
108+
struct MultifileTimeLess {
109+
bool operator()(const struct timespec& t1, const struct timespec& t2) const {
110+
if (t1.tv_sec == t2.tv_sec) {
111+
// If seconds are equal, check nanoseconds
112+
return t1.tv_nsec < t2.tv_nsec;
113+
} else {
114+
// Otherwise, compare seconds
115+
return t1.tv_sec < t2.tv_sec;
116+
}
117+
}
118+
};
119+
120+
// The third parameter here causes all entries to be sorted by access time,
121+
// so oldest will be accessed first in applyLRU
122+
using MultifileEntryStatsMap =
123+
std::multimap<struct timespec, MultifileEntryStats, MultifileTimeLess>;
124+
using MultifileEntryStatsMapIter = MultifileEntryStatsMap::iterator;
125+
#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
126+
107127
class MultifileBlobCache {
108128
public:
109129
MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
@@ -119,6 +139,7 @@ class MultifileBlobCache {
119139

120140
size_t getTotalSize() const { return mTotalCacheSize; }
121141
size_t getTotalEntries() const { return mTotalCacheEntries; }
142+
size_t getTotalCacheSizeDivisor() const { return mTotalCacheSizeDivisor; }
122143

123144
const std::string& getCurrentBuildId() const { return mBuildId; }
124145
void setCurrentBuildId(const std::string& buildId) { mBuildId = buildId; }
@@ -128,10 +149,11 @@ class MultifileBlobCache {
128149

129150
private:
130151
void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
131-
time_t accessTime);
152+
const timespec& accessTime);
132153
bool contains(uint32_t entryHash) const;
133154
bool removeEntry(uint32_t entryHash);
134155
MultifileEntryStats getEntryStats(uint32_t entryHash);
156+
void updateEntryTime(uint32_t entryHash, const timespec& newTime);
135157

136158
bool createStatus(const std::string& baseDir);
137159
bool checkStatus(const std::string& baseDir);
@@ -155,8 +177,14 @@ class MultifileBlobCache {
155177
std::string mBuildId;
156178
uint32_t mCacheVersion;
157179

180+
#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
181+
std::unordered_map<uint32_t, MultifileEntryStatsMapIter> mEntries;
182+
MultifileEntryStatsMap mEntryStats;
183+
#else
158184
std::unordered_set<uint32_t> mEntries;
159185
std::unordered_map<uint32_t, MultifileEntryStats> mEntryStats;
186+
#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
187+
160188
std::unordered_map<uint32_t, MultifileHotCache> mHotCache;
161189

162190
size_t mMaxKeySize;
@@ -165,6 +193,7 @@ class MultifileBlobCache {
165193
size_t mMaxTotalEntries;
166194
size_t mTotalCacheSize;
167195
size_t mTotalCacheEntries;
196+
size_t mTotalCacheSizeDivisor;
168197
size_t mHotCacheLimit;
169198
size_t mHotCacheEntryLimit;
170199
size_t mHotCacheSize;

0 commit comments

Comments
 (0)