Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(153)

Side by Side Diff: Source/wtf/PartitionAlloc.cpp

Issue 133863006: PartitionAlloc: wait just a little while before actually freeing empty pages. (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Review. Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « Source/wtf/PartitionAlloc.h ('k') | Source/wtf/PartitionAllocTest.cpp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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 = -1;
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
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 == -1);
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
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 == -1);
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 != -1) {
596 ASSERT(page->freeCacheIndex >= 0);
597 ASSERT(static_cast<unsigned>(page->freeCacheIndex) < kMaxFreeableSpans);
598 ASSERT(root->globalEmptyPageRing[page->freeCacheIndex] == page);
599 root->globalEmptyPageRing[page->freeCacheIndex] = 0;
600 }
601
602 size_t currentIndex = root->globalEmptyPageRingIndex;
603 PartitionPage* pageToFree = root->globalEmptyPageRing[currentIndex];
604 // The page might well have been re-activated, filled up, etc. before we get
605 // around to looking at it here.
606 if (pageToFree) {
607 ASSERT(pageToFree != &PartitionRootBase::gSeedPage);
608 ASSERT(pageToFree->freeCacheIndex >= 0);
609 ASSERT(static_cast<unsigned>(pageToFree->freeCacheIndex) < kMaxFreeableS pans);
610 ASSERT(pageToFree == root->globalEmptyPageRing[pageToFree->freeCacheInde x]);
611 if (!pageToFree->numAllocatedSlots && pageToFree->freelistHead) {
612 // The page is still empty, and not freed, so _really_ free it.
613 partitionFreePage(pageToFree);
614 }
615 pageToFree->freeCacheIndex = -1;
616 }
617
618 // We put the empty slot span on our global list of "pages that were once
619 // empty". thus providing it a bit of breathing room to get re-used before
620 // we really free it. This improves performance, particularly on Mac OS X
621 // which has subpar memory management performance.
622 root->globalEmptyPageRing[currentIndex] = page;
623 page->freeCacheIndex = currentIndex;
624 ++currentIndex;
625 if (currentIndex == kMaxFreeableSpans)
626 currentIndex = 0;
627 root->globalEmptyPageRingIndex = currentIndex;
628 }
629
591 void partitionFreeSlowPath(PartitionPage* page) 630 void partitionFreeSlowPath(PartitionPage* page)
592 { 631 {
593 PartitionBucket* bucket = page->bucket; 632 PartitionBucket* bucket = page->bucket;
594 ASSERT(page != &PartitionRootGeneric::gSeedPage); 633 ASSERT(page != &PartitionRootGeneric::gSeedPage);
595 ASSERT(bucket->activePagesHead != &PartitionRootGeneric::gSeedPage); 634 ASSERT(bucket->activePagesHead != &PartitionRootGeneric::gSeedPage);
596 if (LIKELY(page->numAllocatedSlots == 0)) { 635 if (LIKELY(page->numAllocatedSlots == 0)) {
597 // Page became fully unused. 636 // Page became fully unused.
598 // If it's the current page, change it! 637 // If it's the current page, attempt to change it. We'd prefer to leave
638 // the page empty as a gentle force towards defragmentation.
599 if (LIKELY(page == bucket->activePagesHead)) { 639 if (LIKELY(page == bucket->activePagesHead)) {
600 // Change active page, rejecting the current page as a candidate. 640 if (partitionSetNewActivePage(bucket, false)) {
601 if (!partitionSetNewActivePage(bucket, false)) { 641 ASSERT(bucket->activePagesHead != page);
602 // We do not free the last active page in a bucket. 642 // 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 643 // avoid losing a reference to it.
604 // a repeating state change between 0 and 1 objects of a given 644 // TODO: consider walking the list to link the empty page after
605 // size allocated; and each state change incurs either a system 645 // all non-empty pages?
606 // call or a page fault, or more. 646 PartitionPage* currentPage = bucket->activePagesHead;
607 ASSERT(!page->nextPage); 647 page->nextPage = currentPage->nextPage;
608 return; 648 currentPage->nextPage = page;
609 } 649 }
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 } 650 }
616 partitionFreePage(page); 651 partitionRegisterEmptyPage(page);
617 } else { 652 } else {
618 // Ensure that the page is full. That's the only valid case if we 653 // Ensure that the page is full. That's the only valid case if we
619 // arrive here. 654 // arrive here.
620 ASSERT(page->numAllocatedSlots < 0); 655 ASSERT(page->numAllocatedSlots < 0);
621 page->numAllocatedSlots = -page->numAllocatedSlots - 2; 656 page->numAllocatedSlots = -page->numAllocatedSlots - 2;
622 ASSERT(page->numAllocatedSlots == static_cast<int>(partitionBucketSlots( bucket) - 1)); 657 ASSERT(page->numAllocatedSlots == static_cast<int>(partitionBucketSlots( bucket) - 1));
623 // Fully used page became partially used. It must be put back on the 658 // 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 659 // 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 660 // chances of it being filled up again. The old current page will be
626 // the next page. 661 // the next page.
627 page->nextPage = bucket->activePagesHead; 662 page->nextPage = bucket->activePagesHead;
628 bucket->activePagesHead = page; 663 bucket->activePagesHead = page;
629 --bucket->numFullPages; 664 --bucket->numFullPages;
630 // Special case: for a partition page with just a single slot, it may 665 // 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. 666 // now be empty and we want to run it through the empty logic.
632 if (UNLIKELY(page->numAllocatedSlots == 0)) 667 if (UNLIKELY(page->numAllocatedSlots == 0))
633 partitionFreeSlowPath(page); 668 partitionFreeSlowPath(page);
634 // Note: there's an opportunity here to look to see if the old active
635 // page is now freeable.
636 } 669 }
637 } 670 }
638 671
639 void* partitionReallocGeneric(PartitionRootGeneric* root, void* ptr, size_t newS ize) 672 void* partitionReallocGeneric(PartitionRootGeneric* root, void* ptr, size_t newS ize)
640 { 673 {
641 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR) 674 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
642 return realloc(ptr, newSize); 675 return realloc(ptr, newSize);
643 #else 676 #else
644 // TODO: note that tcmalloc will "ignore" a downsizing realloc() unless the 677 // 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 678 // 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
768 printf("total live: %zu bytes\n", totalLive); 801 printf("total live: %zu bytes\n", totalLive);
769 printf("total resident: %zu bytes\n", totalResident); 802 printf("total resident: %zu bytes\n", totalResident);
770 printf("total freeable: %zu bytes\n", totalFreeable); 803 printf("total freeable: %zu bytes\n", totalFreeable);
771 fflush(stdout); 804 fflush(stdout);
772 } 805 }
773 806
774 #endif // !NDEBUG 807 #endif // !NDEBUG
775 808
776 } // namespace WTF 809 } // namespace WTF
777 810
OLDNEW
« no previous file with comments | « Source/wtf/PartitionAlloc.h ('k') | Source/wtf/PartitionAllocTest.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698