mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 02:14:43 +00:00
Bug 1249304 - Optimize sorting of CacheIndex::mFrecencyArray, r=honzab
This commit is contained in:
parent
995ce8d997
commit
e700b41079
@ -43,16 +43,29 @@ class FrecencyComparator
|
||||
{
|
||||
public:
|
||||
bool Equals(CacheIndexRecord* a, CacheIndexRecord* b) const {
|
||||
if (!a || !b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return a->mFrecency == b->mFrecency;
|
||||
}
|
||||
bool LessThan(CacheIndexRecord* a, CacheIndexRecord* b) const {
|
||||
// Place entries with frecency 0 at the end of the array.
|
||||
// Removed (=null) entries must be at the end of the array.
|
||||
if (!a) {
|
||||
return false;
|
||||
}
|
||||
if (!b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Place entries with frecency 0 at the end of the non-removed entries.
|
||||
if (a->mFrecency == 0) {
|
||||
return false;
|
||||
}
|
||||
if (b->mFrecency == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return a->mFrecency < b->mFrecency;
|
||||
}
|
||||
};
|
||||
@ -95,19 +108,39 @@ public:
|
||||
}
|
||||
|
||||
if (entry && !mOldRecord) {
|
||||
mIndex->InsertRecordToFrecencyArray(entry->mRec);
|
||||
mIndex->mFrecencyArray.AppendRecord(entry->mRec);
|
||||
mIndex->AddRecordToIterators(entry->mRec);
|
||||
} else if (!entry && mOldRecord) {
|
||||
mIndex->RemoveRecordFromFrecencyArray(mOldRecord);
|
||||
mIndex->mFrecencyArray.RemoveRecord(mOldRecord);
|
||||
mIndex->RemoveRecordFromIterators(mOldRecord);
|
||||
} else if (entry && mOldRecord) {
|
||||
if (entry->mRec != mOldRecord) {
|
||||
// record has a different address, we have to replace it
|
||||
mIndex->ReplaceRecordInIterators(mOldRecord, entry->mRec);
|
||||
mIndex->RemoveRecordFromFrecencyArray(mOldRecord);
|
||||
mIndex->InsertRecordToFrecencyArray(entry->mRec);
|
||||
|
||||
if (entry->mRec->mFrecency == mOldFrecency) {
|
||||
// If frecency hasn't changed simply replace the pointer
|
||||
mIndex->mFrecencyArray.ReplaceRecord(mOldRecord, entry->mRec);
|
||||
} else {
|
||||
// It is expected that when the frecency is changed the new value is
|
||||
// always bigger than the old one. There is also a special case when
|
||||
// we zero the frecency if eviction of the entry fails. If the above
|
||||
// isn't true, this algorithm might not work properly and needs to be
|
||||
// changed.
|
||||
MOZ_ASSERT(entry->mRec->mFrecency == 0 ||
|
||||
entry->mRec->mFrecency > mOldFrecency);
|
||||
|
||||
// Remove old pointer and insert the new one at the end of the array
|
||||
mIndex->mFrecencyArray.RemoveRecord(mOldRecord);
|
||||
mIndex->mFrecencyArray.AppendRecord(entry->mRec);
|
||||
}
|
||||
} else if (entry->mRec->mFrecency != mOldFrecency) {
|
||||
mIndex->mFrecencyArraySorted = false;
|
||||
MOZ_ASSERT(entry->mRec->mFrecency == 0 ||
|
||||
entry->mRec->mFrecency > mOldFrecency);
|
||||
|
||||
// Move the element at the end of the array
|
||||
mIndex->mFrecencyArray.RemoveRecord(entry->mRec);
|
||||
mIndex->mFrecencyArray.AppendRecord(entry->mRec);
|
||||
}
|
||||
} else {
|
||||
// both entries were removed or not initialized, do nothing
|
||||
@ -252,7 +285,6 @@ CacheIndex::CacheIndex()
|
||||
, mRWBufPos(0)
|
||||
, mRWPending(false)
|
||||
, mJournalReadSuccessfully(false)
|
||||
, mFrecencyArraySorted(false)
|
||||
, mAsyncGetDiskConsumptionBlocked(false)
|
||||
{
|
||||
sLock.AssertCurrentThreadOwns();
|
||||
@ -1190,43 +1222,44 @@ CacheIndex::GetEntryForEviction(bool aIgnoreEmptyEntries, SHA1Sum::Hash *aHash,
|
||||
}
|
||||
|
||||
SHA1Sum::Hash hash;
|
||||
bool foundEntry = false;
|
||||
uint32_t i;
|
||||
CacheIndexRecord *foundRecord = nullptr;
|
||||
uint32_t skipped = 0;
|
||||
|
||||
// find first non-forced valid and unpinned entry with the lowest frecency
|
||||
if (!index->mFrecencyArraySorted) {
|
||||
index->mFrecencyArray.Sort(FrecencyComparator());
|
||||
index->mFrecencyArraySorted = true;
|
||||
}
|
||||
index->mFrecencyArray.SortIfNeeded();
|
||||
|
||||
for (i = 0; i < index->mFrecencyArray.Length(); ++i) {
|
||||
memcpy(&hash, &index->mFrecencyArray[i]->mHash, sizeof(SHA1Sum::Hash));
|
||||
for (auto iter = index->mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
|
||||
CacheIndexRecord *rec = iter.Get();
|
||||
|
||||
memcpy(&hash, rec->mHash, sizeof(SHA1Sum::Hash));
|
||||
|
||||
++skipped;
|
||||
|
||||
if (IsForcedValidEntry(&hash)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CacheIndexEntry::IsPinned(index->mFrecencyArray[i])) {
|
||||
if (CacheIndexEntry::IsPinned(rec)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (aIgnoreEmptyEntries &&
|
||||
!CacheIndexEntry::GetFileSize(index->mFrecencyArray[i])) {
|
||||
if (aIgnoreEmptyEntries && !CacheIndexEntry::GetFileSize(rec)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foundEntry = true;
|
||||
--skipped;
|
||||
foundRecord = rec;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!foundEntry)
|
||||
if (!foundRecord)
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
|
||||
*aCnt = index->mFrecencyArray.Length() - i;
|
||||
*aCnt = skipped;
|
||||
|
||||
LOG(("CacheIndex::GetEntryForEviction() - returning entry from frecency "
|
||||
"array [hash=%08x%08x%08x%08x%08x, cnt=%u, frecency=%u]",
|
||||
LOGSHA1(&hash), *aCnt, index->mFrecencyArray[i]->mFrecency));
|
||||
LOGSHA1(&hash), *aCnt, foundRecord->mFrecency));
|
||||
|
||||
memcpy(aHash, &hash, sizeof(SHA1Sum::Hash));
|
||||
|
||||
@ -1320,8 +1353,8 @@ CacheIndex::GetCacheStats(nsILoadContextInfo *aInfo, uint32_t *aSize, uint32_t *
|
||||
*aSize = 0;
|
||||
*aCount = 0;
|
||||
|
||||
for (uint32_t i = 0; i < index->mFrecencyArray.Length(); ++i) {
|
||||
CacheIndexRecord* record = index->mFrecencyArray[i];
|
||||
for (auto iter = index->mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
|
||||
CacheIndexRecord *record = iter.Get();
|
||||
if (!CacheIndexEntry::RecordMatchesLoadContextInfo(record, aInfo))
|
||||
continue;
|
||||
|
||||
@ -1404,22 +1437,21 @@ CacheIndex::GetIterator(nsILoadContextInfo *aInfo, bool aAddNew,
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
RefPtr<CacheIndexIterator> iter;
|
||||
RefPtr<CacheIndexIterator> idxIter;
|
||||
if (aInfo) {
|
||||
iter = new CacheIndexContextIterator(index, aAddNew, aInfo);
|
||||
idxIter = new CacheIndexContextIterator(index, aAddNew, aInfo);
|
||||
} else {
|
||||
iter = new CacheIndexIterator(index, aAddNew);
|
||||
idxIter = new CacheIndexIterator(index, aAddNew);
|
||||
}
|
||||
|
||||
if (!index->mFrecencyArraySorted) {
|
||||
index->mFrecencyArray.Sort(FrecencyComparator());
|
||||
index->mFrecencyArraySorted = true;
|
||||
index->mFrecencyArray.SortIfNeeded();
|
||||
|
||||
for (auto iter = index->mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
|
||||
idxIter->AddRecord(iter.Get());
|
||||
}
|
||||
|
||||
iter->AddRecords(index->mFrecencyArray);
|
||||
|
||||
index->mIterators.AppendElement(iter);
|
||||
iter.swap(*_retval);
|
||||
index->mIterators.AppendElement(idxIter);
|
||||
idxIter.swap(*_retval);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -3231,24 +3263,80 @@ CacheIndex::ReleaseBuffer()
|
||||
}
|
||||
|
||||
void
|
||||
CacheIndex::InsertRecordToFrecencyArray(CacheIndexRecord *aRecord)
|
||||
CacheIndex::FrecencyArray::AppendRecord(CacheIndexRecord *aRecord)
|
||||
{
|
||||
LOG(("CacheIndex::InsertRecordToFrecencyArray() [record=%p, hash=%08x%08x%08x"
|
||||
LOG(("CacheIndex::FrecencyArray::AppendRecord() [record=%p, hash=%08x%08x%08x"
|
||||
"%08x%08x]", aRecord, LOGSHA1(aRecord->mHash)));
|
||||
|
||||
MOZ_ASSERT(!mFrecencyArray.Contains(aRecord));
|
||||
mFrecencyArray.AppendElement(aRecord);
|
||||
mFrecencyArraySorted = false;
|
||||
MOZ_ASSERT(!mRecs.Contains(aRecord));
|
||||
mRecs.AppendElement(aRecord);
|
||||
|
||||
// If the new frecency is 0, the element should be at the end of the array,
|
||||
// i.e. this change doesn't affect order of the array
|
||||
if (aRecord->mFrecency != 0) {
|
||||
++mUnsortedElements;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CacheIndex::RemoveRecordFromFrecencyArray(CacheIndexRecord *aRecord)
|
||||
CacheIndex::FrecencyArray::RemoveRecord(CacheIndexRecord *aRecord)
|
||||
{
|
||||
LOG(("CacheIndex::RemoveRecordFromFrecencyArray() [record=%p]", aRecord));
|
||||
LOG(("CacheIndex::FrecencyArray::RemoveRecord() [record=%p]", aRecord));
|
||||
|
||||
DebugOnly<bool> removed;
|
||||
removed = mFrecencyArray.RemoveElement(aRecord);
|
||||
MOZ_ASSERT(removed);
|
||||
decltype(mRecs)::index_type idx;
|
||||
idx = mRecs.IndexOf(aRecord);
|
||||
MOZ_RELEASE_ASSERT(idx != mRecs.NoIndex);
|
||||
mRecs[idx] = nullptr;
|
||||
++mRemovedElements;
|
||||
|
||||
// Calling SortIfNeeded ensures that we get rid of removed elements in the
|
||||
// array once we hit the limit.
|
||||
SortIfNeeded();
|
||||
}
|
||||
|
||||
void
|
||||
CacheIndex::FrecencyArray::ReplaceRecord(CacheIndexRecord *aOldRecord,
|
||||
CacheIndexRecord *aNewRecord)
|
||||
{
|
||||
LOG(("CacheIndex::FrecencyArray::ReplaceRecord() [oldRecord=%p, "
|
||||
"newRecord=%p]", aOldRecord, aNewRecord));
|
||||
|
||||
decltype(mRecs)::index_type idx;
|
||||
idx = mRecs.IndexOf(aOldRecord);
|
||||
MOZ_RELEASE_ASSERT(idx != mRecs.NoIndex);
|
||||
mRecs[idx] = aNewRecord;
|
||||
}
|
||||
|
||||
void
|
||||
CacheIndex::FrecencyArray::SortIfNeeded()
|
||||
{
|
||||
const uint32_t kMaxUnsortedCount = 512;
|
||||
const uint32_t kMaxUnsortedPercent = 10;
|
||||
const uint32_t kMaxRemovedCount = 512;
|
||||
|
||||
uint32_t unsortedLimit =
|
||||
std::min<uint32_t>(kMaxUnsortedCount, Length() * kMaxUnsortedPercent / 100);
|
||||
|
||||
if (mUnsortedElements > unsortedLimit ||
|
||||
mRemovedElements > kMaxRemovedCount) {
|
||||
LOG(("CacheIndex::FrecencyArray::SortIfNeeded() - Sorting array "
|
||||
"[unsortedElements=%u, unsortedLimit=%u, removedElements=%u, "
|
||||
"maxRemovedCount=%u]", mUnsortedElements, unsortedLimit,
|
||||
mRemovedElements, kMaxRemovedCount));
|
||||
|
||||
mRecs.Sort(FrecencyComparator());
|
||||
mUnsortedElements = 0;
|
||||
if (mRemovedElements) {
|
||||
#ifdef DEBUG
|
||||
for (uint32_t i = Length(); i < mRecs.Length(); ++i) {
|
||||
MOZ_ASSERT(!mRecs[i]);
|
||||
}
|
||||
#endif
|
||||
// Removed elements are at the end after sorting.
|
||||
mRecs.RemoveElementsAt(Length(), mRemovedElements);
|
||||
mRemovedElements = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -3626,7 +3714,7 @@ CacheIndex::SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) cons
|
||||
n += mTmpJournal.SizeOfExcludingThis(mallocSizeOf);
|
||||
|
||||
// mFrecencyArray items are reported by mIndex/mPendingUpdates
|
||||
n += mFrecencyArray.ShallowSizeOfExcludingThis(mallocSizeOf);
|
||||
n += mFrecencyArray.mRecs.ShallowSizeOfExcludingThis(mallocSizeOf);
|
||||
n += mDiskConsumptionObservers.ShallowSizeOfExcludingThis(mallocSizeOf);
|
||||
|
||||
return n;
|
||||
@ -3710,7 +3798,9 @@ CacheIndex::ReportHashStats()
|
||||
}
|
||||
|
||||
nsTArray<CacheIndexRecord *> records;
|
||||
records.AppendElements(mFrecencyArray);
|
||||
for (auto iter = mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
|
||||
records.AppendElement(iter.Get());
|
||||
}
|
||||
|
||||
records.Sort(HashComparator());
|
||||
|
||||
|
@ -921,10 +921,6 @@ private:
|
||||
void AllocBuffer();
|
||||
void ReleaseBuffer();
|
||||
|
||||
// Methods used by CacheIndexEntryAutoManage to keep the arrays up to date.
|
||||
void InsertRecordToFrecencyArray(CacheIndexRecord *aRecord);
|
||||
void RemoveRecordFromFrecencyArray(CacheIndexRecord *aRecord);
|
||||
|
||||
// Methods used by CacheIndexEntryAutoManage to keep the iterators up to date.
|
||||
void AddRecordToIterators(CacheIndexRecord *aRecord);
|
||||
void RemoveRecordFromIterators(CacheIndexRecord *aRecord);
|
||||
@ -1031,12 +1027,79 @@ private:
|
||||
// of the journal fails or the hash does not match.
|
||||
nsTHashtable<CacheIndexEntry> mTmpJournal;
|
||||
|
||||
// An array that keeps entry records ordered by eviction preference; we take
|
||||
// the entry with lowest valid frecency. Zero frecency is an initial value
|
||||
// and such entries are stored at the end of the array. Uninitialized entries
|
||||
// and entries marked as deleted are not present in this array.
|
||||
nsTArray<CacheIndexRecord *> mFrecencyArray;
|
||||
bool mFrecencyArraySorted;
|
||||
// FrecencyArray maintains order of entry records for eviction. Ideally, the
|
||||
// records would be ordered by frecency all the time, but since this would be
|
||||
// quite expensive, we allow certain amount of entries to be out of order.
|
||||
// When the frecency is updated the new value is always bigger than the old
|
||||
// one. Instead of keeping updated entries at the same position, we move them
|
||||
// at the end of the array. This protects recently updated entries from
|
||||
// eviction. The array is sorted once we hit the limit of maximum unsorted
|
||||
// entries.
|
||||
class FrecencyArray
|
||||
{
|
||||
class Iterator
|
||||
{
|
||||
public:
|
||||
explicit Iterator(nsTArray<CacheIndexRecord *> *aRecs)
|
||||
: mRecs(aRecs)
|
||||
, mIdx(0)
|
||||
{
|
||||
while (!Done() && !(*mRecs)[mIdx]) {
|
||||
mIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
bool Done() const { return mIdx == mRecs->Length(); }
|
||||
|
||||
CacheIndexRecord* Get() const
|
||||
{
|
||||
MOZ_ASSERT(!Done());
|
||||
return (*mRecs)[mIdx];
|
||||
}
|
||||
|
||||
void Next()
|
||||
{
|
||||
MOZ_ASSERT(!Done());
|
||||
++mIdx;
|
||||
while (!Done() && !(*mRecs)[mIdx]) {
|
||||
mIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
nsTArray<CacheIndexRecord *> *mRecs;
|
||||
uint32_t mIdx;
|
||||
};
|
||||
|
||||
public:
|
||||
Iterator Iter() { return Iterator(&mRecs); }
|
||||
|
||||
FrecencyArray() : mUnsortedElements(0)
|
||||
, mRemovedElements(0) {}
|
||||
|
||||
// Methods used by CacheIndexEntryAutoManage to keep the array up to date.
|
||||
void AppendRecord(CacheIndexRecord *aRecord);
|
||||
void RemoveRecord(CacheIndexRecord *aRecord);
|
||||
void ReplaceRecord(CacheIndexRecord *aOldRecord,
|
||||
CacheIndexRecord *aNewRecord);
|
||||
void SortIfNeeded();
|
||||
|
||||
size_t Length() const { return mRecs.Length() - mRemovedElements; }
|
||||
void Clear() { mRecs.Clear(); }
|
||||
|
||||
private:
|
||||
friend class CacheIndex;
|
||||
|
||||
nsTArray<CacheIndexRecord *> mRecs;
|
||||
uint32_t mUnsortedElements;
|
||||
// Instead of removing elements from the array immediately, we null them out
|
||||
// and the iterator skips them when accessing the array. The null pointers
|
||||
// are placed at the end during sorting and we strip them out all at once.
|
||||
// This saves moving a lot of memory in nsTArray::RemoveElementsAt.
|
||||
uint32_t mRemovedElements;
|
||||
};
|
||||
|
||||
FrecencyArray mFrecencyArray;
|
||||
|
||||
nsTArray<CacheIndexIterator *> mIterators;
|
||||
|
||||
|
@ -90,14 +90,6 @@ CacheIndexIterator::AddRecord(CacheIndexRecord *aRecord)
|
||||
mRecords.AppendElement(aRecord);
|
||||
}
|
||||
|
||||
void
|
||||
CacheIndexIterator::AddRecords(const nsTArray<CacheIndexRecord *> &aRecords)
|
||||
{
|
||||
LOG(("CacheIndexIterator::AddRecords() [this=%p]", this));
|
||||
|
||||
mRecords.AppendElements(aRecords);
|
||||
}
|
||||
|
||||
bool
|
||||
CacheIndexIterator::RemoveRecord(CacheIndexRecord *aRecord)
|
||||
{
|
||||
|
@ -43,7 +43,6 @@ protected:
|
||||
|
||||
bool ShouldBeNewAdded() { return mAddNew; }
|
||||
virtual void AddRecord(CacheIndexRecord *aRecord);
|
||||
virtual void AddRecords(const nsTArray<CacheIndexRecord *> &aRecords);
|
||||
bool RemoveRecord(CacheIndexRecord *aRecord);
|
||||
bool ReplaceRecord(CacheIndexRecord *aOldRecord,
|
||||
CacheIndexRecord *aNewRecord);
|
||||
|
Loading…
Reference in New Issue
Block a user