Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright (C) 2013 Google Inc. All rights reserved. | 2 * Copyright (C) 2013 Google Inc. All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
| 6 * met: | 6 * met: |
| 7 * | 7 * |
| 8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 54 | 54 |
| 55 namespace WTF { | 55 namespace WTF { |
| 56 | 56 |
| 57 int PartitionRootBase::gInitializedLock = 0; | 57 int PartitionRootBase::gInitializedLock = 0; |
| 58 bool PartitionRootBase::gInitialized = false; | 58 bool PartitionRootBase::gInitialized = false; |
| 59 PartitionPage PartitionRootBase::gSeedPage; | 59 PartitionPage PartitionRootBase::gSeedPage; |
| 60 PartitionBucket PartitionRootBase::gPagedBucket; | 60 PartitionBucket PartitionRootBase::gPagedBucket; |
| 61 | 61 |
| 62 static size_t partitionBucketNumSystemPages(size_t size) | 62 static size_t partitionBucketNumSystemPages(size_t size) |
| 63 { | 63 { |
| 64 // TODO: Remove this once the more comprehensive solution is landed. | |
| 65 // This is a temporary bandaid to fix a Mac performance issue. Mac has poor page | |
| 66 // fault performance so we limit the freelist thrash of these popular sizes by | |
| 67 // allocating them in bigger slabs. | |
| 68 if (size == 16384 || size == 8192 || size == 4096) | |
| 69 return kMaxSystemPagesPerSlotSpan; | |
| 70 // This works out reasonably for the current bucket sizes of the generic | 64 // This works out reasonably for the current bucket sizes of the generic |
| 71 // allocator, and the current values of partition page size and constants. | 65 // allocator, and the current values of partition page size and constants. |
| 72 // Specifically, we have enough room to always pack the slots perfectly into | 66 // Specifically, we have enough room to always pack the slots perfectly into |
| 73 // some number of system pages. The only waste is the waste associated with | 67 // some number of system pages. The only waste is the waste associated with |
| 74 // unfaulted pages (i.e. wasted address space). | 68 // unfaulted pages (i.e. wasted address space). |
| 75 // TODO: we end up using a lot of system pages for very small sizes. For | 69 // TODO: we end up using a lot of system pages for very small sizes. For |
| 76 // example, we'll use 12 system pages for slot size 24. The slot size is | 70 // example, we'll use 12 system pages for slot size 24. The slot size is |
| 77 // so small that the waste would be tiny with just 4, or 1, system pages. | 71 // so small that the waste would be tiny with just 4, or 1, system pages. |
| 78 // Later, we can investigate whether there are anti-fragmentation benefits | 72 // Later, we can investigate whether there are anti-fragmentation benefits |
| 79 // to using fewer system pages. | 73 // to using fewer system pages. |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 113 spinLockUnlock(&PartitionRootBase::gInitializedLock); | 107 spinLockUnlock(&PartitionRootBase::gInitializedLock); |
| 114 | 108 |
| 115 root->initialized = true; | 109 root->initialized = true; |
| 116 root->totalSizeOfSuperPages = 0; | 110 root->totalSizeOfSuperPages = 0; |
| 117 root->nextSuperPage = 0; | 111 root->nextSuperPage = 0; |
| 118 root->nextPartitionPage = 0; | 112 root->nextPartitionPage = 0; |
| 119 root->nextPartitionPageEnd = 0; | 113 root->nextPartitionPageEnd = 0; |
| 120 root->firstExtent = 0; | 114 root->firstExtent = 0; |
| 121 root->currentExtent = 0; | 115 root->currentExtent = 0; |
| 122 | 116 |
| 117 root->globalEmptyPageHeadIndex = 0; | |
| 118 root->numGlobalEmptyPageEntries = 0; | |
| 119 | |
| 123 // This is a "magic" value so we can test if a root pointer is valid. | 120 // This is a "magic" value so we can test if a root pointer is valid. |
| 124 root->invertedSelf = ~reinterpret_cast<uintptr_t>(root); | 121 root->invertedSelf = ~reinterpret_cast<uintptr_t>(root); |
| 125 } | 122 } |
| 126 | 123 |
| 127 static void partitionBucketInitBase(PartitionBucket* bucket, PartitionRootBase* root) | 124 static void partitionBucketInitBase(PartitionBucket* bucket, PartitionRootBase* root) |
| 128 { | 125 { |
| 129 bucket->activePagesHead = &PartitionRootGeneric::gSeedPage; | 126 bucket->activePagesHead = &PartitionRootGeneric::gSeedPage; |
| 130 bucket->freePagesHead = 0; | 127 bucket->freePagesHead = 0; |
| 131 bucket->numFullPages = 0; | 128 bucket->numFullPages = 0; |
| 132 bucket->numSystemPagesPerSlotSpan = partitionBucketNumSystemPages(bucket->sl otSize); | 129 bucket->numSystemPagesPerSlotSpan = partitionBucketNumSystemPages(bucket->sl otSize); |
| (...skipping 254 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 387 { | 384 { |
| 388 ASSERT(page != &PartitionRootGeneric::gSeedPage); | 385 ASSERT(page != &PartitionRootGeneric::gSeedPage); |
| 389 page->numAllocatedSlots = 0; | 386 page->numAllocatedSlots = 0; |
| 390 page->numUnprovisionedSlots = partitionBucketSlots(bucket); | 387 page->numUnprovisionedSlots = partitionBucketSlots(bucket); |
| 391 ASSERT(page->numUnprovisionedSlots); | 388 ASSERT(page->numUnprovisionedSlots); |
| 392 page->bucket = bucket; | 389 page->bucket = bucket; |
| 393 page->nextPage = 0; | 390 page->nextPage = 0; |
| 394 // NULLing the freelist is not strictly necessary but it makes an ASSERT in partitionPageFillFreelist simpler. | 391 // NULLing the freelist is not strictly necessary but it makes an ASSERT in partitionPageFillFreelist simpler. |
| 395 page->freelistHead = 0; | 392 page->freelistHead = 0; |
| 396 page->pageOffset = 0; | 393 page->pageOffset = 0; |
| 394 page->freeCacheIndex = 0; | |
| 397 size_t numPartitionPages = partitionBucketPartitionPages(bucket); | 395 size_t numPartitionPages = partitionBucketPartitionPages(bucket); |
| 398 size_t i; | 396 size_t i; |
| 399 char* pageCharPtr = reinterpret_cast<char*>(page); | 397 char* pageCharPtr = reinterpret_cast<char*>(page); |
| 400 for (i = 1; i < numPartitionPages; ++i) { | 398 for (i = 1; i < numPartitionPages; ++i) { |
| 401 pageCharPtr += kPageMetadataSize; | 399 pageCharPtr += kPageMetadataSize; |
| 402 PartitionPage* secondaryPage = reinterpret_cast<PartitionPage*>(pageChar Ptr); | 400 PartitionPage* secondaryPage = reinterpret_cast<PartitionPage*>(pageChar Ptr); |
| 403 secondaryPage->pageOffset = i; | 401 secondaryPage->pageOffset = i; |
| 404 } | 402 } |
| 405 } | 403 } |
| 406 | 404 |
| (...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 492 | 490 |
| 493 // Page is usable if it has something on the freelist, or unprovisioned | 491 // Page is usable if it has something on the freelist, or unprovisioned |
| 494 // slots that can be turned into a freelist. | 492 // slots that can be turned into a freelist. |
| 495 if (LIKELY(page->freelistHead != 0) || LIKELY(page->numUnprovisionedSlot s)) { | 493 if (LIKELY(page->freelistHead != 0) || LIKELY(page->numUnprovisionedSlot s)) { |
| 496 bucket->activePagesHead = page; | 494 bucket->activePagesHead = page; |
| 497 return true; | 495 return true; |
| 498 } | 496 } |
| 499 | 497 |
| 500 ASSERT(page->numAllocatedSlots >= 0); | 498 ASSERT(page->numAllocatedSlots >= 0); |
| 501 if (LIKELY(page->numAllocatedSlots == 0)) { | 499 if (LIKELY(page->numAllocatedSlots == 0)) { |
| 500 ASSERT(!page->freeCacheIndex); | |
| 502 // We hit a free page, so shepherd it on to the free page list. | 501 // We hit a free page, so shepherd it on to the free page list. |
| 503 page->nextPage = bucket->freePagesHead; | 502 page->nextPage = bucket->freePagesHead; |
| 504 bucket->freePagesHead = page; | 503 bucket->freePagesHead = page; |
| 505 } else { | 504 } else { |
| 506 // If we get here, we found a full page. Skip over it too, and also | 505 // If we get here, we found a full page. Skip over it too, and also |
| 507 // tag it as full (via a negative value). We need it tagged so that | 506 // tag it as full (via a negative value). We need it tagged so that |
| 508 // free'ing can tell, and move it back into the active page list. | 507 // free'ing can tell, and move it back into the active page list. |
| 509 ASSERT(page->numAllocatedSlots == static_cast<int>(partitionBucketSl ots(bucket))); | 508 ASSERT(page->numAllocatedSlots == static_cast<int>(partitionBucketSl ots(bucket))); |
| 510 page->numAllocatedSlots = -page->numAllocatedSlots; | 509 page->numAllocatedSlots = -page->numAllocatedSlots; |
| 511 ++bucket->numFullPages; | 510 ++bucket->numFullPages; |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 553 return partitionPageAllocAndFillFreelist(newPage); | 552 return partitionPageAllocAndFillFreelist(newPage); |
| 554 } | 553 } |
| 555 | 554 |
| 556 // Second, look in our list of freed but reserved pages. | 555 // Second, look in our list of freed but reserved pages. |
| 557 PartitionPage* newPage = bucket->freePagesHead; | 556 PartitionPage* newPage = bucket->freePagesHead; |
| 558 if (LIKELY(newPage != 0)) { | 557 if (LIKELY(newPage != 0)) { |
| 559 ASSERT(newPage != &PartitionRootGeneric::gSeedPage); | 558 ASSERT(newPage != &PartitionRootGeneric::gSeedPage); |
| 560 ASSERT(!newPage->freelistHead); | 559 ASSERT(!newPage->freelistHead); |
| 561 ASSERT(!newPage->numAllocatedSlots); | 560 ASSERT(!newPage->numAllocatedSlots); |
| 562 ASSERT(!newPage->numUnprovisionedSlots); | 561 ASSERT(!newPage->numUnprovisionedSlots); |
| 562 ASSERT(!newPage->freeCacheIndex); | |
| 563 bucket->freePagesHead = newPage->nextPage; | 563 bucket->freePagesHead = newPage->nextPage; |
| 564 } else { | 564 } else { |
| 565 // Third. If we get here, we need a brand new page. | 565 // Third. If we get here, we need a brand new page. |
| 566 size_t numPartitionPages = partitionBucketPartitionPages(bucket); | 566 size_t numPartitionPages = partitionBucketPartitionPages(bucket); |
| 567 void* rawNewPage = partitionAllocPartitionPages(root, numPartitionPages) ; | 567 void* rawNewPage = partitionAllocPartitionPages(root, numPartitionPages) ; |
| 568 // Skip the alignment check because it depends on page->bucket, which is not yet set. | 568 // Skip the alignment check because it depends on page->bucket, which is not yet set. |
| 569 newPage = partitionPointerToPageNoAlignmentCheck(rawNewPage); | 569 newPage = partitionPointerToPageNoAlignmentCheck(rawNewPage); |
| 570 } | 570 } |
| 571 | 571 |
| 572 partitionPageReset(newPage, bucket); | 572 partitionPageReset(newPage, bucket); |
| 573 bucket->activePagesHead = newPage; | 573 bucket->activePagesHead = newPage; |
| 574 return partitionPageAllocAndFillFreelist(newPage); | 574 return partitionPageAllocAndFillFreelist(newPage); |
| 575 } | 575 } |
| 576 | 576 |
| 577 static ALWAYS_INLINE void partitionFreePage(PartitionPage* page) | 577 static ALWAYS_INLINE void partitionFreePage(PartitionPage* page) |
| 578 { | 578 { |
| 579 ASSERT(page->freelistHead); | 579 ASSERT(page->freelistHead); |
| 580 ASSERT(!page->numAllocatedSlots); | 580 ASSERT(!page->numAllocatedSlots); |
| 581 partitionUnusePage(page); | 581 partitionUnusePage(page); |
| 582 // We actually leave the freed page in the active list. We'll sweep it on | 582 // We actually leave the freed page in the active list. We'll sweep it on |
| 583 // to the free page list when we next walk the active page list. Pulling | 583 // to the free page list when we next walk the active page list. Pulling |
| 584 // this trick enables us to use a singly-linked page list for all cases, | 584 // this trick enables us to use a singly-linked page list for all cases, |
| 585 // which is critical in keeping the page metadata structure down to 32 | 585 // which is critical in keeping the page metadata structure down to 32 |
| 586 // bytes in size. | 586 // bytes in size. |
| 587 page->freelistHead = 0; | 587 page->freelistHead = 0; |
| 588 page->numUnprovisionedSlots = 0; | 588 page->numUnprovisionedSlots = 0; |
| 589 } | 589 } |
| 590 | 590 |
| 591 static ALWAYS_INLINE void partitionRegisterEmptyPage(PartitionPage* page) | |
| 592 { | |
| 593 PartitionRootBase* root = partitionPageToRoot(page); | |
| 594 // If the page is already registered as empty, give it another life. | |
| 595 if (page->freeCacheIndex) { | |
| 596 ASSERT(page->freeCacheIndex <= kMaxFreeableSpans); | |
| 597 ASSERT(root->globalEmptyPageRing[page->freeCacheIndex - 1] == page); | |
| 598 root->globalEmptyPageRing[page->freeCacheIndex - 1] = 0; | |
| 599 } | |
| 600 | |
| 601 ASSERT(root->numGlobalEmptyPageEntries <= kMaxFreeableSpans); | |
| 602 if (root->numGlobalEmptyPageEntries == kMaxFreeableSpans) { | |
| 603 // Too many freeable pages in the system, must handle one. | |
| 604 PartitionPage* pageToFree = root->globalEmptyPageRing[root->globalEmptyP ageHeadIndex]; | |
| 605 // The page might well have been re-activated, filled up, etc. before we | |
| 606 // get around to looking at it here. | |
| 607 ASSERT(pageToFree != &PartitionRootBase::gSeedPage); | |
| 608 ASSERT(!pageToFree || pageToFree == root->globalEmptyPageRing[pageToFree ->freeCacheIndex - 1]); | |
| 609 --root->numGlobalEmptyPageEntries; | |
| 610 ++root->globalEmptyPageHeadIndex; | |
| 611 if (root->globalEmptyPageHeadIndex == kMaxFreeableSpans) | |
| 612 root->globalEmptyPageHeadIndex = 0; | |
| 613 if (pageToFree) { | |
| 614 if (!pageToFree->numAllocatedSlots && pageToFree->freelistHead) { | |
| 615 // The page is still empty, and not freed, so _really_ free it. | |
| 616 partitionFreePage(pageToFree); | |
| 617 } | |
| 618 pageToFree->freeCacheIndex = 0; | |
| 619 } | |
| 620 } | |
| 621 | |
| 622 // We put the empty slot span on our global list of "pages that were once | |
| 623 // empty". thus providing it a bit of breathing room to get re-used before | |
| 624 // we really free it. This improves performance, particularly on Mac OS X | |
| 625 // which has subpar memory management performance. | |
| 626 size_t tailIndex = root->globalEmptyPageHeadIndex + root->numGlobalEmptyPage Entries; | |
| 627 if (tailIndex >= kMaxFreeableSpans) | |
| 628 tailIndex -= kMaxFreeableSpans; | |
|
Tom Sepez
2014/01/14 22:17:10
nit: assert that it's in range after subtracting.
| |
| 629 root->globalEmptyPageRing[tailIndex] = page; | |
| 630 ++root->numGlobalEmptyPageEntries; | |
| 631 page->freeCacheIndex = tailIndex + 1; | |
| 632 } | |
| 633 | |
| 591 void partitionFreeSlowPath(PartitionPage* page) | 634 void partitionFreeSlowPath(PartitionPage* page) |
| 592 { | 635 { |
| 593 PartitionBucket* bucket = page->bucket; | 636 PartitionBucket* bucket = page->bucket; |
| 594 ASSERT(page != &PartitionRootGeneric::gSeedPage); | 637 ASSERT(page != &PartitionRootGeneric::gSeedPage); |
| 595 ASSERT(bucket->activePagesHead != &PartitionRootGeneric::gSeedPage); | 638 ASSERT(bucket->activePagesHead != &PartitionRootGeneric::gSeedPage); |
| 596 if (LIKELY(page->numAllocatedSlots == 0)) { | 639 if (LIKELY(page->numAllocatedSlots == 0)) { |
| 597 // Page became fully unused. | 640 // Page became fully unused. |
| 598 // If it's the current page, change it! | 641 // If it's the current page, attempt to change it. We'd prefer to leave |
| 642 // the page empty as a gentle force towards defragmentation. | |
| 599 if (LIKELY(page == bucket->activePagesHead)) { | 643 if (LIKELY(page == bucket->activePagesHead)) { |
| 600 // Change active page, rejecting the current page as a candidate. | 644 if (partitionSetNewActivePage(bucket, false)) { |
| 601 if (!partitionSetNewActivePage(bucket, false)) { | 645 ASSERT(bucket->activePagesHead != page); |
| 602 // We do not free the last active page in a bucket. | 646 // Link the empty page back in after the new current page, to |
| 603 // To do so is a serious performance issue as we can get into | 647 // avoid losing a reference to it. |
| 604 // a repeating state change between 0 and 1 objects of a given | 648 // TODO: consider walking the list to link the empty page after |
| 605 // size allocated; and each state change incurs either a system | 649 // all non-empty pages? |
| 606 // call or a page fault, or more. | 650 PartitionPage* currentPage = bucket->activePagesHead; |
| 607 ASSERT(!page->nextPage); | 651 page->nextPage = currentPage->nextPage; |
| 608 return; | 652 currentPage->nextPage = page; |
| 609 } | 653 } |
| 610 // Link the to-be-freed page back in after the new current page, to | |
| 611 // avoid losing a reference to it. | |
| 612 PartitionPage* currentPage = bucket->activePagesHead; | |
| 613 page->nextPage = currentPage->nextPage; | |
| 614 currentPage->nextPage = page; | |
| 615 } | 654 } |
| 616 partitionFreePage(page); | 655 partitionRegisterEmptyPage(page); |
| 617 } else { | 656 } else { |
| 618 // Ensure that the page is full. That's the only valid case if we | 657 // Ensure that the page is full. That's the only valid case if we |
| 619 // arrive here. | 658 // arrive here. |
| 620 ASSERT(page->numAllocatedSlots < 0); | 659 ASSERT(page->numAllocatedSlots < 0); |
| 621 page->numAllocatedSlots = -page->numAllocatedSlots - 2; | 660 page->numAllocatedSlots = -page->numAllocatedSlots - 2; |
| 622 ASSERT(page->numAllocatedSlots == static_cast<int>(partitionBucketSlots( bucket) - 1)); | 661 ASSERT(page->numAllocatedSlots == static_cast<int>(partitionBucketSlots( bucket) - 1)); |
| 623 // Fully used page became partially used. It must be put back on the | 662 // Fully used page became partially used. It must be put back on the |
| 624 // non-full page list. Also make it the current page to increase the | 663 // non-full page list. Also make it the current page to increase the |
| 625 // chances of it being filled up again. The old current page will be | 664 // chances of it being filled up again. The old current page will be |
| 626 // the next page. | 665 // the next page. |
| 627 page->nextPage = bucket->activePagesHead; | 666 page->nextPage = bucket->activePagesHead; |
| 628 bucket->activePagesHead = page; | 667 bucket->activePagesHead = page; |
| 629 --bucket->numFullPages; | 668 --bucket->numFullPages; |
| 630 // Special case: for a partition page with just a single slot, it may | 669 // Special case: for a partition page with just a single slot, it may |
| 631 // now be empty and we want to run it through the empty logic. | 670 // now be empty and we want to run it through the empty logic. |
| 632 if (UNLIKELY(page->numAllocatedSlots == 0)) | 671 if (UNLIKELY(page->numAllocatedSlots == 0)) |
| 633 partitionFreeSlowPath(page); | 672 partitionFreeSlowPath(page); |
| 634 // Note: there's an opportunity here to look to see if the old active | |
| 635 // page is now freeable. | |
| 636 } | 673 } |
| 637 } | 674 } |
| 638 | 675 |
| 639 void* partitionReallocGeneric(PartitionRootGeneric* root, void* ptr, size_t newS ize) | 676 void* partitionReallocGeneric(PartitionRootGeneric* root, void* ptr, size_t newS ize) |
| 640 { | 677 { |
| 641 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR) | 678 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR) |
| 642 return realloc(ptr, newSize); | 679 return realloc(ptr, newSize); |
| 643 #else | 680 #else |
| 644 // TODO: note that tcmalloc will "ignore" a downsizing realloc() unless the | 681 // TODO: note that tcmalloc will "ignore" a downsizing realloc() unless the |
| 645 // new size is a significant percentage smaller. We could do the same if we | 682 // new size is a significant percentage smaller. We could do the same if we |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 768 printf("total live: %zu bytes\n", totalLive); | 805 printf("total live: %zu bytes\n", totalLive); |
| 769 printf("total resident: %zu bytes\n", totalResident); | 806 printf("total resident: %zu bytes\n", totalResident); |
| 770 printf("total freeable: %zu bytes\n", totalFreeable); | 807 printf("total freeable: %zu bytes\n", totalFreeable); |
| 771 fflush(stdout); | 808 fflush(stdout); |
| 772 } | 809 } |
| 773 | 810 |
| 774 #endif // !NDEBUG | 811 #endif // !NDEBUG |
| 775 | 812 |
| 776 } // namespace WTF | 813 } // namespace WTF |
| 777 | 814 |
| OLD | NEW |