Chromium Code Reviews| Index: Source/wtf/PartitionAlloc.cpp |
| diff --git a/Source/wtf/PartitionAlloc.cpp b/Source/wtf/PartitionAlloc.cpp |
| index b4b4f6a1c7833ccad39775f938d058a0ef22977e..458d99c5e039c9bd022910b8b9ef6ecc36a9bf70 100644 |
| --- a/Source/wtf/PartitionAlloc.cpp |
| +++ b/Source/wtf/PartitionAlloc.cpp |
| @@ -61,12 +61,6 @@ PartitionBucket PartitionRootBase::gPagedBucket; |
| static size_t partitionBucketNumSystemPages(size_t size) |
| { |
| - // TODO: Remove this once the more comprehensive solution is landed. |
| - // This is a temporary bandaid to fix a Mac performance issue. Mac has poor page |
| - // fault performance so we limit the freelist thrash of these popular sizes by |
| - // allocating them in bigger slabs. |
| - if (size == 16384 || size == 8192 || size == 4096) |
| - return kMaxSystemPagesPerSlotSpan; |
| // This works out reasonably for the current bucket sizes of the generic |
| // allocator, and the current values of partition page size and constants. |
| // Specifically, we have enough room to always pack the slots perfectly into |
| @@ -120,6 +114,9 @@ static void parititonAllocBaseInit(PartitionRootBase* root) |
| root->firstExtent = 0; |
| root->currentExtent = 0; |
| + root->globalEmptyPageHeadIndex = 0; |
| + root->numGlobalEmptyPageEntries = 0; |
| + |
| // This is a "magic" value so we can test if a root pointer is valid. |
| root->invertedSelf = ~reinterpret_cast<uintptr_t>(root); |
| } |
| @@ -394,6 +391,7 @@ static ALWAYS_INLINE void partitionPageReset(PartitionPage* page, PartitionBucke |
| // NULLing the freelist is not strictly necessary but it makes an ASSERT in partitionPageFillFreelist simpler. |
| page->freelistHead = 0; |
| page->pageOffset = 0; |
| + page->freeCacheIndex = 0; |
| size_t numPartitionPages = partitionBucketPartitionPages(bucket); |
| size_t i; |
| char* pageCharPtr = reinterpret_cast<char*>(page); |
| @@ -499,6 +497,7 @@ static ALWAYS_INLINE bool partitionSetNewActivePage(PartitionBucket* bucket, boo |
| ASSERT(page->numAllocatedSlots >= 0); |
| if (LIKELY(page->numAllocatedSlots == 0)) { |
| + ASSERT(!page->freeCacheIndex); |
| // We hit a free page, so shepherd it on to the free page list. |
| page->nextPage = bucket->freePagesHead; |
| bucket->freePagesHead = page; |
| @@ -560,6 +559,7 @@ void* partitionAllocSlowPath(PartitionRootBase* root, size_t size, PartitionBuck |
| ASSERT(!newPage->freelistHead); |
| ASSERT(!newPage->numAllocatedSlots); |
| ASSERT(!newPage->numUnprovisionedSlots); |
| + ASSERT(!newPage->freeCacheIndex); |
| bucket->freePagesHead = newPage->nextPage; |
| } else { |
| // Third. If we get here, we need a brand new page. |
| @@ -588,6 +588,49 @@ static ALWAYS_INLINE void partitionFreePage(PartitionPage* page) |
| page->numUnprovisionedSlots = 0; |
| } |
| +static ALWAYS_INLINE void partitionRegisterEmptyPage(PartitionPage* page) |
| +{ |
| + PartitionRootBase* root = partitionPageToRoot(page); |
| + // If the page is already registered as empty, give it another life. |
| + if (page->freeCacheIndex) { |
| + ASSERT(page->freeCacheIndex <= kMaxFreeableSpans); |
| + ASSERT(root->globalEmptyPageRing[page->freeCacheIndex - 1] == page); |
| + root->globalEmptyPageRing[page->freeCacheIndex - 1] = 0; |
| + } |
| + |
| + ASSERT(root->numGlobalEmptyPageEntries <= kMaxFreeableSpans); |
| + if (root->numGlobalEmptyPageEntries == kMaxFreeableSpans) { |
| + // Too many freeable pages in the system, must handle one. |
| + PartitionPage* pageToFree = root->globalEmptyPageRing[root->globalEmptyPageHeadIndex]; |
| + // The page might well have been re-activated, filled up, etc. before we |
| + // get around to looking at it here. |
| + ASSERT(pageToFree != &PartitionRootBase::gSeedPage); |
| + ASSERT(!pageToFree || pageToFree == root->globalEmptyPageRing[pageToFree->freeCacheIndex - 1]); |
| + --root->numGlobalEmptyPageEntries; |
| + ++root->globalEmptyPageHeadIndex; |
| + if (root->globalEmptyPageHeadIndex == kMaxFreeableSpans) |
| + root->globalEmptyPageHeadIndex = 0; |
| + if (pageToFree) { |
| + if (!pageToFree->numAllocatedSlots && pageToFree->freelistHead) { |
| + // The page is still empty, and not freed, so _really_ free it. |
| + partitionFreePage(pageToFree); |
| + } |
| + pageToFree->freeCacheIndex = 0; |
| + } |
| + } |
| + |
| + // We put the empty slot span on our global list of "pages that were once |
| + // empty". thus providing it a bit of breathing room to get re-used before |
| + // we really free it. This improves performance, particularly on Mac OS X |
| + // which has subpar memory management performance. |
| + size_t tailIndex = root->globalEmptyPageHeadIndex + root->numGlobalEmptyPageEntries; |
| + if (tailIndex >= kMaxFreeableSpans) |
| + tailIndex -= kMaxFreeableSpans; |
|
Tom Sepez
2014/01/14 22:17:10
nit: assert that it's in range after subtracting.
|
| + root->globalEmptyPageRing[tailIndex] = page; |
| + ++root->numGlobalEmptyPageEntries; |
| + page->freeCacheIndex = tailIndex + 1; |
| +} |
| + |
| void partitionFreeSlowPath(PartitionPage* page) |
| { |
| PartitionBucket* bucket = page->bucket; |
| @@ -595,25 +638,21 @@ void partitionFreeSlowPath(PartitionPage* page) |
| ASSERT(bucket->activePagesHead != &PartitionRootGeneric::gSeedPage); |
| if (LIKELY(page->numAllocatedSlots == 0)) { |
| // Page became fully unused. |
| - // If it's the current page, change it! |
| + // If it's the current page, attempt to change it. We'd prefer to leave |
| + // the page empty as a gentle force towards defragmentation. |
| if (LIKELY(page == bucket->activePagesHead)) { |
| - // Change active page, rejecting the current page as a candidate. |
| - if (!partitionSetNewActivePage(bucket, false)) { |
| - // We do not free the last active page in a bucket. |
| - // To do so is a serious performance issue as we can get into |
| - // a repeating state change between 0 and 1 objects of a given |
| - // size allocated; and each state change incurs either a system |
| - // call or a page fault, or more. |
| - ASSERT(!page->nextPage); |
| - return; |
| + if (partitionSetNewActivePage(bucket, false)) { |
| + ASSERT(bucket->activePagesHead != page); |
| + // Link the empty page back in after the new current page, to |
| + // avoid losing a reference to it. |
| + // TODO: consider walking the list to link the empty page after |
| + // all non-empty pages? |
| + PartitionPage* currentPage = bucket->activePagesHead; |
| + page->nextPage = currentPage->nextPage; |
| + currentPage->nextPage = page; |
| } |
| - // Link the to-be-freed page back in after the new current page, to |
| - // avoid losing a reference to it. |
| - PartitionPage* currentPage = bucket->activePagesHead; |
| - page->nextPage = currentPage->nextPage; |
| - currentPage->nextPage = page; |
| } |
| - partitionFreePage(page); |
| + partitionRegisterEmptyPage(page); |
| } else { |
| // Ensure that the page is full. That's the only valid case if we |
| // arrive here. |
| @@ -631,8 +670,6 @@ void partitionFreeSlowPath(PartitionPage* page) |
| // now be empty and we want to run it through the empty logic. |
| if (UNLIKELY(page->numAllocatedSlots == 0)) |
| partitionFreeSlowPath(page); |
| - // Note: there's an opportunity here to look to see if the old active |
| - // page is now freeable. |
| } |
| } |