From 46e842db3df49b7d12ec93b1858c9de571ed585d Mon Sep 17 00:00:00 2001 From: "bryner%netscape.com" Date: Mon, 17 Mar 2003 23:22:29 +0000 Subject: [PATCH] Implement an LRU-SP eviction policy for the memory cache (bug 188458). r=gordon, sr=darin. git-svn-id: svn://10.0.0.236/trunk@139625 18797224-902f-48f8-a5cc-f745e15eee43 --- .../netwerk/cache/src/nsMemoryCacheDevice.cpp | 78 ++++++++++++++++--- .../netwerk/cache/src/nsMemoryCacheDevice.h | 12 ++- 2 files changed, 76 insertions(+), 14 deletions(-) diff --git a/mozilla/netwerk/cache/src/nsMemoryCacheDevice.cpp b/mozilla/netwerk/cache/src/nsMemoryCacheDevice.cpp index d601b1b479c..32707458282 100644 --- a/mozilla/netwerk/cache/src/nsMemoryCacheDevice.cpp +++ b/mozilla/netwerk/cache/src/nsMemoryCacheDevice.cpp @@ -20,6 +20,7 @@ * Contributor(s): * Gordon Sheridan, * Patrick C. Beard + * Brian Ryner */ #include "nsMemoryCacheDevice.h" @@ -29,21 +30,29 @@ #include "nsICacheVisitor.h" #include "nsCRT.h" +// The memory cache implements the "LRU-SP" caching algorithm described in +// "LRU-SP: A Size-Adjusted and Popularity-Aware LRU Replacement Algorithm +// for Web Caching" by Kai Cheng and Yahiko Kambayashi. + +// We keep ceil(log2(mHardLimit+1)) LRU queues. The queues hold exponentially +// increasing ranges of floor(log2((size/nref))) values for entries. const char *gMemoryDeviceID = "memory"; nsMemoryCacheDevice::nsMemoryCacheDevice() : mInitialized(PR_FALSE), + mEvictionList(nsnull), mEvictionThreshold(40 * 1024), mHardLimit(4 * 1024 * 1024), // set default memory limit, in case prefs aren't available mTotalSize(0), mInactiveSize(0), mEntryCount(0), - mMaxEntryCount(0) + mMaxEntryCount(0), + mQueueCount(0) { - PR_INIT_CLIST(&mEvictionList[mostLikelyToEvict]); - PR_INIT_CLIST(&mEvictionList[leastLikelyToEvict]); + // mEvictionList is created lazily to avoid re-creating it if the + // capacity is set immediately. } @@ -79,7 +88,7 @@ nsMemoryCacheDevice::Shutdown() // evict all entries nsCacheEntry * entry, * next; - for (int i=mostLikelyToEvict; i <= leastLikelyToEvict; ++i) { + for (int i = mQueueCount - 1; i >= 0; --i) { entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]); while (entry != &mEvictionList[i]) { NS_ASSERTION(entry->IsInUse() == PR_FALSE, "### shutting down with active entries.\n"); @@ -105,6 +114,10 @@ nsMemoryCacheDevice::Shutdown() NS_ASSERTION(mEntryCount == 0, "### mem cache leaking entries?\n"); mInitialized = PR_FALSE; + + delete[] mEvictionList; + mEvictionList = nsnull; + return NS_OK; } @@ -124,6 +137,7 @@ nsMemoryCacheDevice::FindEntry(nsCString * key) // move entry to the tail of an eviction list PR_REMOVE_AND_INIT_LINK(entry); + EnsureEvictionLists(); PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]); mInactiveSize -= entry->Size(); @@ -167,6 +181,7 @@ nsMemoryCacheDevice::BindEntry(nsCacheEntry * entry) NS_ASSERTION(PR_CLIST_IS_EMPTY(entry),"entry is already on a list!"); // append entry to the eviction list + EnsureEvictionLists(); PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]); // add entry to hashtable of mem cache entries @@ -289,6 +304,7 @@ nsMemoryCacheDevice::OnDataSizeChange( nsCacheEntry * entry, PRInt32 deltaSize) if (!entry->IsDoomed()) { // move entry to the tail of the appropriate eviction list PR_REMOVE_AND_INIT_LINK(entry); + EnsureEvictionLists(); PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, deltaSize)]); } @@ -303,7 +319,26 @@ nsMemoryCacheDevice::AdjustMemoryLimits(PRInt32 softLimit, PRInt32 hardLimit) { mSoftLimit = softLimit; mHardLimit = hardLimit; + + // First, evict entries that won't fit into the new cache size. EvictEntriesIfNecessary(); + + // Create the eviction list array at the new size and then insert + // all of the entries into it. + PRInt32 oldQueueCount = mQueueCount; + PRCList* oldEvictionList = mEvictionList; + CreateEvictionLists(); + + for (PRInt32 i = PR_MIN(oldQueueCount, mQueueCount) - 1; i >= 0; --i) { + nsCacheEntry* entry = (nsCacheEntry*) PR_LIST_HEAD(&oldEvictionList[i]); + while (entry != &oldEvictionList[i]) { + nsCacheEntry* next = (nsCacheEntry*) PR_NEXT_LINK(entry); + PR_APPEND_LINK(entry, &mEvictionList[i]); + entry = next; + } + } + + delete[] oldEvictionList; } @@ -334,7 +369,7 @@ nsMemoryCacheDevice::EvictEntriesIfNecessary(void) if ((mTotalSize < mHardLimit) && (mInactiveSize < mSoftLimit)) return; - for (int i=mostLikelyToEvict; i<=leastLikelyToEvict; ++i) { + for (int i = mQueueCount - 1; i >= 0; --i) { entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]); while (entry != &mEvictionList[i]) { if (entry->IsInUse()) { @@ -357,10 +392,14 @@ int nsMemoryCacheDevice::EvictionList(nsCacheEntry * entry, PRInt32 deltaSize) { PRInt32 size = deltaSize + (PRInt32)entry->Size(); - if ((size > mEvictionThreshold) || (entry->ExpirationTime() != NO_EXPIRATION_TIME)) - return mostLikelyToEvict; - - return leastLikelyToEvict; + + // favor items which never expire by putting them in the lowest-index queue + if (entry->ExpirationTime() == NO_EXPIRATION_TIME) + return 0; + + // compute which eviction queue this entry should go into, based on + // floor(log2(size/nref)) + return PR_FloorLog2(size / (entry->FetchCount() + 1)); } @@ -381,7 +420,7 @@ nsMemoryCacheDevice::Visit(nsICacheVisitor * visitor) nsCacheEntry * entry; nsCOMPtr entryRef; - for (int i=mostLikelyToEvict; i <= leastLikelyToEvict; ++i) { + for (int i = mQueueCount - 1; i >= 0; --i) { entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]); while (entry != &mEvictionList[i]) { nsCacheEntryInfo * entryInfo = new nsCacheEntryInfo(entry); @@ -406,7 +445,7 @@ nsMemoryCacheDevice::EvictEntries(const char * clientID) nsCacheEntry * entry; PRUint32 prefixLength = (clientID ? strlen(clientID) : 0); - for (int i=mostLikelyToEvict; i<=leastLikelyToEvict; ++i) { + for (int i = mQueueCount - 1; i >= 0; --i) { PRCList * elem = PR_LIST_HEAD(&mEvictionList[i]); while (elem != &mEvictionList[i]) { entry = (nsCacheEntry *)elem; @@ -436,6 +475,23 @@ nsMemoryCacheDevice::SetCapacity(PRInt32 capacity) AdjustMemoryLimits(softLimit, hardLimit); } +inline void +nsMemoryCacheDevice::EnsureEvictionLists() +{ + if (!mEvictionList) + CreateEvictionLists(); +} + +void +nsMemoryCacheDevice::CreateEvictionLists() +{ + // Create log2 (mHardLimit + 1) LRU queues + + mQueueCount = PR_CeilingLog2(mHardLimit + 1); + mEvictionList = new PRCList[mQueueCount]; + for (int i = 0; i < mQueueCount; ++i) + PR_INIT_CLIST(&mEvictionList[i]); +} /****************************************************************************** * nsMemoryCacheDeviceInfo - for implementing about:cache diff --git a/mozilla/netwerk/cache/src/nsMemoryCacheDevice.h b/mozilla/netwerk/cache/src/nsMemoryCacheDevice.h index a7130cc5cc7..318deab4907 100644 --- a/mozilla/netwerk/cache/src/nsMemoryCacheDevice.h +++ b/mozilla/netwerk/cache/src/nsMemoryCacheDevice.h @@ -19,6 +19,7 @@ * * Contributor(s): * Gordon Sheridan, 20-February-2001 + * Brian Ryner */ #ifndef _nsMemoryCacheDevice_h_ @@ -78,15 +79,17 @@ private: void EvictEntriesIfNecessary(); int EvictionList(nsCacheEntry * entry, PRInt32 deltaSize); + void EnsureEvictionLists(); + void CreateEvictionLists(); + /* * Data members */ nsCacheEntryHashTable mMemCacheEntries; PRBool mInitialized; - - enum { mostLikelyToEvict = 0, leastLikelyToEvict = 1 }; // constants to differentiate eviction lists - PRCList mEvictionList[2]; + + PRCList* mEvictionList; PRInt32 mEvictionThreshold; PRInt32 mHardLimit; @@ -98,6 +101,9 @@ private: PRInt32 mEntryCount; PRInt32 mMaxEntryCount; + + PRInt32 mQueueCount; + // XXX what other stats do we want to keep? };