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 memset(&root->globalEmptyPageRing, '\0', sizeof(root->globalEmptyPageRing)); | |
118 root->globalEmptyPageRingIndex = 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 size_t currentIndex = root->globalEmptyPageRingIndex; | |
602 PartitionPage* pageToFree = root->globalEmptyPageRing[currentIndex]; | |
603 // The page might well have been re-activated, filled up, etc. before we get | |
604 // around to looking at it here. | |
605 ASSERT(pageToFree != &PartitionRootBase::gSeedPage); | |
606 ASSERT(!pageToFree || pageToFree == root->globalEmptyPageRing[pageToFree->fr eeCacheIndex - 1]); | |
Tom Sepez
2014/01/14 23:08:47
nit: mabye this assert moves inside if (pageToFree
| |
607 if (pageToFree) { | |
608 if (!pageToFree->numAllocatedSlots && pageToFree->freelistHead) { | |
609 // The page is still empty, and not freed, so _really_ free it. | |
610 partitionFreePage(pageToFree); | |
611 } | |
612 pageToFree->freeCacheIndex = 0; | |
613 } | |
614 | |
615 // We put the empty slot span on our global list of "pages that were once | |
616 // empty". thus providing it a bit of breathing room to get re-used before | |
617 // we really free it. This improves performance, particularly on Mac OS X | |
618 // which has subpar memory management performance. | |
619 root->globalEmptyPageRing[currentIndex] = page; | |
620 page->freeCacheIndex = currentIndex + 1; | |
Tom Sepez
2014/01/14 23:08:47
why +1 ? Maybe 0xffff as magic value not in slot?
| |
621 ++currentIndex; | |
Tom Sepez
2014/01/14 23:08:47
nit: do currentIndex++ first, then assign to avoid
| |
622 if (currentIndex == kMaxFreeableSpans) | |
623 currentIndex = 0; | |
624 root->globalEmptyPageRingIndex = currentIndex; | |
625 } | |
626 | |
591 void partitionFreeSlowPath(PartitionPage* page) | 627 void partitionFreeSlowPath(PartitionPage* page) |
592 { | 628 { |
593 PartitionBucket* bucket = page->bucket; | 629 PartitionBucket* bucket = page->bucket; |
594 ASSERT(page != &PartitionRootGeneric::gSeedPage); | 630 ASSERT(page != &PartitionRootGeneric::gSeedPage); |
595 ASSERT(bucket->activePagesHead != &PartitionRootGeneric::gSeedPage); | 631 ASSERT(bucket->activePagesHead != &PartitionRootGeneric::gSeedPage); |
596 if (LIKELY(page->numAllocatedSlots == 0)) { | 632 if (LIKELY(page->numAllocatedSlots == 0)) { |
597 // Page became fully unused. | 633 // Page became fully unused. |
598 // If it's the current page, change it! | 634 // If it's the current page, attempt to change it. We'd prefer to leave |
635 // the page empty as a gentle force towards defragmentation. | |
599 if (LIKELY(page == bucket->activePagesHead)) { | 636 if (LIKELY(page == bucket->activePagesHead)) { |
600 // Change active page, rejecting the current page as a candidate. | 637 if (partitionSetNewActivePage(bucket, false)) { |
601 if (!partitionSetNewActivePage(bucket, false)) { | 638 ASSERT(bucket->activePagesHead != page); |
602 // We do not free the last active page in a bucket. | 639 // 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 | 640 // avoid losing a reference to it. |
604 // a repeating state change between 0 and 1 objects of a given | 641 // TODO: consider walking the list to link the empty page after |
605 // size allocated; and each state change incurs either a system | 642 // all non-empty pages? |
606 // call or a page fault, or more. | 643 PartitionPage* currentPage = bucket->activePagesHead; |
607 ASSERT(!page->nextPage); | 644 page->nextPage = currentPage->nextPage; |
608 return; | 645 currentPage->nextPage = page; |
609 } | 646 } |
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 } | 647 } |
616 partitionFreePage(page); | 648 partitionRegisterEmptyPage(page); |
617 } else { | 649 } else { |
618 // Ensure that the page is full. That's the only valid case if we | 650 // Ensure that the page is full. That's the only valid case if we |
619 // arrive here. | 651 // arrive here. |
620 ASSERT(page->numAllocatedSlots < 0); | 652 ASSERT(page->numAllocatedSlots < 0); |
621 page->numAllocatedSlots = -page->numAllocatedSlots - 2; | 653 page->numAllocatedSlots = -page->numAllocatedSlots - 2; |
622 ASSERT(page->numAllocatedSlots == static_cast<int>(partitionBucketSlots( bucket) - 1)); | 654 ASSERT(page->numAllocatedSlots == static_cast<int>(partitionBucketSlots( bucket) - 1)); |
623 // Fully used page became partially used. It must be put back on the | 655 // 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 | 656 // 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 | 657 // chances of it being filled up again. The old current page will be |
626 // the next page. | 658 // the next page. |
627 page->nextPage = bucket->activePagesHead; | 659 page->nextPage = bucket->activePagesHead; |
628 bucket->activePagesHead = page; | 660 bucket->activePagesHead = page; |
629 --bucket->numFullPages; | 661 --bucket->numFullPages; |
630 // Special case: for a partition page with just a single slot, it may | 662 // 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. | 663 // now be empty and we want to run it through the empty logic. |
632 if (UNLIKELY(page->numAllocatedSlots == 0)) | 664 if (UNLIKELY(page->numAllocatedSlots == 0)) |
633 partitionFreeSlowPath(page); | 665 partitionFreeSlowPath(page); |
634 // Note: there's an opportunity here to look to see if the old active | |
635 // page is now freeable. | |
636 } | 666 } |
637 } | 667 } |
638 | 668 |
639 void* partitionReallocGeneric(PartitionRootGeneric* root, void* ptr, size_t newS ize) | 669 void* partitionReallocGeneric(PartitionRootGeneric* root, void* ptr, size_t newS ize) |
640 { | 670 { |
641 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR) | 671 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR) |
642 return realloc(ptr, newSize); | 672 return realloc(ptr, newSize); |
643 #else | 673 #else |
644 // TODO: note that tcmalloc will "ignore" a downsizing realloc() unless the | 674 // 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 | 675 // 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); | 798 printf("total live: %zu bytes\n", totalLive); |
769 printf("total resident: %zu bytes\n", totalResident); | 799 printf("total resident: %zu bytes\n", totalResident); |
770 printf("total freeable: %zu bytes\n", totalFreeable); | 800 printf("total freeable: %zu bytes\n", totalFreeable); |
771 fflush(stdout); | 801 fflush(stdout); |
772 } | 802 } |
773 | 803 |
774 #endif // !NDEBUG | 804 #endif // !NDEBUG |
775 | 805 |
776 } // namespace WTF | 806 } // namespace WTF |
777 | 807 |
OLD | NEW |