| Index: third_party/tcmalloc/page_heap.cc
|
| ===================================================================
|
| --- third_party/tcmalloc/page_heap.cc (revision 0)
|
| +++ third_party/tcmalloc/page_heap.cc (revision 0)
|
| @@ -0,0 +1,495 @@
|
| +// Copyright (c) 2008, Google Inc.
|
| +// All rights reserved.
|
| +//
|
| +// Redistribution and use in source and binary forms, with or without
|
| +// modification, are permitted provided that the following conditions are
|
| +// met:
|
| +//
|
| +// * Redistributions of source code must retain the above copyright
|
| +// notice, this list of conditions and the following disclaimer.
|
| +// * Redistributions in binary form must reproduce the above
|
| +// copyright notice, this list of conditions and the following disclaimer
|
| +// in the documentation and/or other materials provided with the
|
| +// distribution.
|
| +// * Neither the name of Google Inc. nor the names of its
|
| +// contributors may be used to endorse or promote products derived from
|
| +// this software without specific prior written permission.
|
| +//
|
| +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
| +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
| +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
| +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| +
|
| +// ---
|
| +// Author: Sanjay Ghemawat <opensource@google.com>
|
| +
|
| +#include <config.h>
|
| +#include "page_heap.h"
|
| +
|
| +#include "static_vars.h"
|
| +#include "system-alloc.h"
|
| +
|
| +DEFINE_double(tcmalloc_release_rate,
|
| + EnvToDouble("TCMALLOC_RELEASE_RATE", 1.0),
|
| + "Rate at which we release unused memory to the system. "
|
| + "Zero means we never release memory back to the system. "
|
| + "Increase this flag to return memory faster; decrease it "
|
| + "to return memory slower. Reasonable rates are in the "
|
| + "range [0,10]");
|
| +
|
| +namespace tcmalloc {
|
| +
|
| +PageHeap::PageHeap()
|
| + : pagemap_(MetaDataAlloc),
|
| + pagemap_cache_(0),
|
| + free_pages_(0),
|
| + system_bytes_(0),
|
| + scavenge_counter_(0),
|
| + // Start scavenging at kMaxPages list
|
| + scavenge_index_(kMaxPages-1) {
|
| + COMPILE_ASSERT(kNumClasses <= (1 << PageMapCache::kValuebits), valuebits);
|
| + DLL_Init(&large_.normal);
|
| + DLL_Init(&large_.returned);
|
| + for (int i = 0; i < kMaxPages; i++) {
|
| + DLL_Init(&free_[i].normal);
|
| + DLL_Init(&free_[i].returned);
|
| + }
|
| +}
|
| +
|
| +Span* PageHeap::New(Length n) {
|
| + ASSERT(Check());
|
| + ASSERT(n > 0);
|
| +
|
| + // Find first size >= n that has a non-empty list
|
| + for (Length s = n; s < kMaxPages; s++) {
|
| + Span* ll = &free_[s].normal;
|
| + // If we're lucky, ll is non-empty, meaning it has a suitable span.
|
| + if (!DLL_IsEmpty(ll)) {
|
| + ASSERT(ll->next->location == Span::ON_NORMAL_FREELIST);
|
| + return Carve(ll->next, n);
|
| + }
|
| + // Alternatively, maybe there's a usable returned span.
|
| + ll = &free_[s].returned;
|
| + if (!DLL_IsEmpty(ll)) {
|
| + ASSERT(ll->next->location == Span::ON_RETURNED_FREELIST);
|
| + return Carve(ll->next, n);
|
| + }
|
| + // Still no luck, so keep looking in larger classes.
|
| + }
|
| +
|
| + Span* result = AllocLarge(n);
|
| + if (result != NULL) return result;
|
| +
|
| + // Grow the heap and try again
|
| + if (!GrowHeap(n)) {
|
| + ASSERT(Check());
|
| + return NULL;
|
| + }
|
| +
|
| + return AllocLarge(n);
|
| +}
|
| +
|
| +Span* PageHeap::AllocLarge(Length n) {
|
| + // find the best span (closest to n in size).
|
| + // The following loops implements address-ordered best-fit.
|
| + Span *best = NULL;
|
| +
|
| + // Search through normal list
|
| + for (Span* span = large_.normal.next;
|
| + span != &large_.normal;
|
| + span = span->next) {
|
| + if (span->length >= n) {
|
| + if ((best == NULL)
|
| + || (span->length < best->length)
|
| + || ((span->length == best->length) && (span->start < best->start))) {
|
| + best = span;
|
| + ASSERT(best->location == Span::ON_NORMAL_FREELIST);
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Search through released list in case it has a better fit
|
| + for (Span* span = large_.returned.next;
|
| + span != &large_.returned;
|
| + span = span->next) {
|
| + if (span->length >= n) {
|
| + if ((best == NULL)
|
| + || (span->length < best->length)
|
| + || ((span->length == best->length) && (span->start < best->start))) {
|
| + best = span;
|
| + ASSERT(best->location == Span::ON_RETURNED_FREELIST);
|
| + }
|
| + }
|
| + }
|
| +
|
| + return best == NULL ? NULL : Carve(best, n);
|
| +}
|
| +
|
| +Span* PageHeap::Split(Span* span, Length n) {
|
| + ASSERT(0 < n);
|
| + ASSERT(n < span->length);
|
| + ASSERT(span->location == Span::IN_USE);
|
| + ASSERT(span->sizeclass == 0);
|
| + Event(span, 'T', n);
|
| +
|
| + const int extra = span->length - n;
|
| + Span* leftover = NewSpan(span->start + n, extra);
|
| + ASSERT(leftover->location == Span::IN_USE);
|
| + Event(leftover, 'U', extra);
|
| + RecordSpan(leftover);
|
| + pagemap_.set(span->start + n - 1, span); // Update map from pageid to span
|
| + span->length = n;
|
| +
|
| + return leftover;
|
| +}
|
| +
|
| +Span* PageHeap::Carve(Span* span, Length n) {
|
| + ASSERT(n > 0);
|
| + ASSERT(span->location != Span::IN_USE);
|
| + const int old_location = span->location;
|
| + DLL_Remove(span);
|
| + span->location = Span::IN_USE;
|
| + Event(span, 'A', n);
|
| +
|
| + const int extra = span->length - n;
|
| + ASSERT(extra >= 0);
|
| + if (extra > 0) {
|
| + Span* leftover = NewSpan(span->start + n, extra);
|
| + leftover->location = old_location;
|
| + Event(leftover, 'S', extra);
|
| + RecordSpan(leftover);
|
| +
|
| + // Place leftover span on appropriate free list
|
| + SpanList* listpair = (extra < kMaxPages) ? &free_[extra] : &large_;
|
| + Span* dst = (leftover->location == Span::ON_RETURNED_FREELIST
|
| + ? &listpair->returned : &listpair->normal);
|
| + DLL_Prepend(dst, leftover);
|
| +
|
| + span->length = n;
|
| + pagemap_.set(span->start + n - 1, span);
|
| + }
|
| + ASSERT(Check());
|
| + free_pages_ -= n;
|
| + if (old_location == Span::ON_RETURNED_FREELIST) {
|
| + // We need to recommit this address space.
|
| + TCMalloc_SystemCommit(
|
| + reinterpret_cast<void*>(span->start << kPageShift),
|
| + static_cast<size_t>(span->length << kPageShift));
|
| + }
|
| + return span;
|
| +}
|
| +
|
| +void PageHeap::Delete(Span* span) {
|
| + ASSERT(Check());
|
| + ASSERT(span->location == Span::IN_USE);
|
| + ASSERT(span->length > 0);
|
| + ASSERT(GetDescriptor(span->start) == span);
|
| + ASSERT(GetDescriptor(span->start + span->length - 1) == span);
|
| + span->sizeclass = 0;
|
| + span->sample = 0;
|
| +
|
| + // Coalesce -- we guarantee that "p" != 0, so no bounds checking
|
| + // necessary. We do not bother resetting the stale pagemap
|
| + // entries for the pieces we are merging together because we only
|
| + // care about the pagemap entries for the boundaries.
|
| + //
|
| + // Note that the spans we merge into "span" may come out of
|
| + // a "normal" list. For simplicity, we move these into the
|
| + // "returned" list of the appropriate size class. We do this
|
| + // so that we can maximize large, continuous blocks of freed
|
| + // space.
|
| + const PageID p = span->start;
|
| + const Length n = span->length;
|
| + Span* prev = GetDescriptor(p-1);
|
| + if (prev != NULL && prev->location != Span::IN_USE) {
|
| + // Merge preceding span into this span
|
| + ASSERT(prev->start + prev->length == p);
|
| + const Length len = prev->length;
|
| + DLL_Remove(prev);
|
| + DeleteSpan(prev);
|
| + span->start -= len;
|
| + span->length += len;
|
| + pagemap_.set(span->start, span);
|
| + Event(span, 'L', len);
|
| + }
|
| + Span* next = GetDescriptor(p+n);
|
| + if (next != NULL && next->location != Span::IN_USE) {
|
| + // Merge next span into this span
|
| + ASSERT(next->start == p+n);
|
| + const Length len = next->length;
|
| + DLL_Remove(next);
|
| + DeleteSpan(next);
|
| + span->length += len;
|
| + pagemap_.set(span->start + span->length - 1, span);
|
| + Event(span, 'R', len);
|
| + }
|
| +
|
| + Event(span, 'D', span->length);
|
| + span->location = Span::ON_RETURNED_FREELIST;
|
| + TCMalloc_SystemRelease(reinterpret_cast<void*>(span->start << kPageShift),
|
| + static_cast<size_t>(span->length << kPageShift));
|
| + if (span->length < kMaxPages)
|
| + DLL_Prepend(&free_[span->length].returned, span);
|
| + else
|
| + DLL_Prepend(&large_.returned, span);
|
| + free_pages_ += n;
|
| +
|
| + IncrementalScavenge(n);
|
| + ASSERT(Check());
|
| +}
|
| +
|
| +void PageHeap::IncrementalScavenge(Length n) {
|
| + // Fast path; not yet time to release memory
|
| + scavenge_counter_ -= n;
|
| + if (scavenge_counter_ >= 0) return; // Not yet time to scavenge
|
| +
|
| + // Never delay scavenging for more than the following number of
|
| + // deallocated pages. With 4K pages, this comes to 4GB of
|
| + // deallocation.
|
| + // Chrome: Changed to 64MB
|
| + static const int kMaxReleaseDelay = 1 << 14;
|
| +
|
| + // If there is nothing to release, wait for so many pages before
|
| + // scavenging again. With 4K pages, this comes to 1GB of memory.
|
| + // Chrome: Changed to 16MB
|
| + static const int kDefaultReleaseDelay = 1 << 12;
|
| +
|
| + const double rate = FLAGS_tcmalloc_release_rate;
|
| + if (rate <= 1e-6) {
|
| + // Tiny release rate means that releasing is disabled.
|
| + scavenge_counter_ = kDefaultReleaseDelay;
|
| + return;
|
| + }
|
| +
|
| + // Find index of free list to scavenge
|
| + int index = scavenge_index_ + 1;
|
| + for (int i = 0; i < kMaxPages+1; i++) {
|
| + if (index > kMaxPages) index = 0;
|
| + SpanList* slist = (index == kMaxPages) ? &large_ : &free_[index];
|
| + if (!DLL_IsEmpty(&slist->normal)) {
|
| + // Release the last span on the normal portion of this list
|
| + Span* s = slist->normal.prev;
|
| + ASSERT(s->location == Span::ON_NORMAL_FREELIST);
|
| + DLL_Remove(s);
|
| + TCMalloc_SystemRelease(reinterpret_cast<void*>(s->start << kPageShift),
|
| + static_cast<size_t>(s->length << kPageShift));
|
| + s->location = Span::ON_RETURNED_FREELIST;
|
| + DLL_Prepend(&slist->returned, s);
|
| +
|
| + // Compute how long to wait until we return memory.
|
| + // FLAGS_tcmalloc_release_rate==1 means wait for 1000 pages
|
| + // after releasing one page.
|
| + const double mult = 1000.0 / rate;
|
| + double wait = mult * static_cast<double>(s->length);
|
| + if (wait > kMaxReleaseDelay) {
|
| + // Avoid overflow and bound to reasonable range
|
| + wait = kMaxReleaseDelay;
|
| + }
|
| + scavenge_counter_ = static_cast<int64_t>(wait);
|
| +
|
| + scavenge_index_ = index; // Scavenge at index+1 next time
|
| + // Note: we stop scavenging after finding one.
|
| + return;
|
| + }
|
| + index++;
|
| + }
|
| +
|
| + // Nothing to scavenge, delay for a while
|
| + scavenge_counter_ = kDefaultReleaseDelay;
|
| +}
|
| +
|
| +void PageHeap::RegisterSizeClass(Span* span, size_t sc) {
|
| + // Associate span object with all interior pages as well
|
| + ASSERT(span->location == Span::IN_USE);
|
| + ASSERT(GetDescriptor(span->start) == span);
|
| + ASSERT(GetDescriptor(span->start+span->length-1) == span);
|
| + Event(span, 'C', sc);
|
| + span->sizeclass = sc;
|
| + for (Length i = 1; i < span->length-1; i++) {
|
| + pagemap_.set(span->start+i, span);
|
| + }
|
| +}
|
| +
|
| +static double PagesToMB(uint64_t pages) {
|
| + return (pages << kPageShift) / 1048576.0;
|
| +}
|
| +
|
| +void PageHeap::Dump(TCMalloc_Printer* out) {
|
| + int nonempty_sizes = 0;
|
| + for (int s = 0; s < kMaxPages; s++) {
|
| + if (!DLL_IsEmpty(&free_[s].normal) || !DLL_IsEmpty(&free_[s].returned)) {
|
| + nonempty_sizes++;
|
| + }
|
| + }
|
| + out->printf("------------------------------------------------\n");
|
| + out->printf("PageHeap: %d sizes; %6.1f MB free\n",
|
| + nonempty_sizes, PagesToMB(free_pages_));
|
| + out->printf("------------------------------------------------\n");
|
| + uint64_t total_normal = 0;
|
| + uint64_t total_returned = 0;
|
| + for (int s = 0; s < kMaxPages; s++) {
|
| + const int n_length = DLL_Length(&free_[s].normal);
|
| + const int r_length = DLL_Length(&free_[s].returned);
|
| + if (n_length + r_length > 0) {
|
| + uint64_t n_pages = s * n_length;
|
| + uint64_t r_pages = s * r_length;
|
| + total_normal += n_pages;
|
| + total_returned += r_pages;
|
| + out->printf("%6u pages * %6u spans ~ %6.1f MB; %6.1f MB cum"
|
| + "; unmapped: %6.1f MB; %6.1f MB cum\n",
|
| + s,
|
| + (n_length + r_length),
|
| + PagesToMB(n_pages + r_pages),
|
| + PagesToMB(total_normal + total_returned),
|
| + PagesToMB(r_pages),
|
| + PagesToMB(total_returned));
|
| + }
|
| + }
|
| +
|
| + uint64_t n_pages = 0;
|
| + uint64_t r_pages = 0;
|
| + int n_spans = 0;
|
| + int r_spans = 0;
|
| + out->printf("Normal large spans:\n");
|
| + for (Span* s = large_.normal.next; s != &large_.normal; s = s->next) {
|
| + out->printf(" [ %6" PRIuPTR " pages ] %6.1f MB\n",
|
| + s->length, PagesToMB(s->length));
|
| + n_pages += s->length;
|
| + n_spans++;
|
| + }
|
| + out->printf("Unmapped large spans:\n");
|
| + for (Span* s = large_.returned.next; s != &large_.returned; s = s->next) {
|
| + out->printf(" [ %6" PRIuPTR " pages ] %6.1f MB\n",
|
| + s->length, PagesToMB(s->length));
|
| + r_pages += s->length;
|
| + r_spans++;
|
| + }
|
| + total_normal += n_pages;
|
| + total_returned += r_pages;
|
| + out->printf(">255 large * %6u spans ~ %6.1f MB; %6.1f MB cum"
|
| + "; unmapped: %6.1f MB; %6.1f MB cum\n",
|
| + (n_spans + r_spans),
|
| + PagesToMB(n_pages + r_pages),
|
| + PagesToMB(total_normal + total_returned),
|
| + PagesToMB(r_pages),
|
| + PagesToMB(total_returned));
|
| +}
|
| +
|
| +static void RecordGrowth(size_t growth) {
|
| + StackTrace* t = Static::stacktrace_allocator()->New();
|
| + t->depth = GetStackTrace(t->stack, kMaxStackDepth-1, 3);
|
| + t->size = growth;
|
| + t->stack[kMaxStackDepth-1] = reinterpret_cast<void*>(Static::growth_stacks());
|
| + Static::set_growth_stacks(t);
|
| +}
|
| +
|
| +bool PageHeap::GrowHeap(Length n) {
|
| + ASSERT(kMaxPages >= kMinSystemAlloc);
|
| + if (n > kMaxValidPages) return false;
|
| + Length ask = (n>kMinSystemAlloc) ? n : static_cast<Length>(kMinSystemAlloc);
|
| + size_t actual_size;
|
| + void* ptr = TCMalloc_SystemAlloc(ask << kPageShift, &actual_size, kPageSize);
|
| + if (ptr == NULL) {
|
| + if (n < ask) {
|
| + // Try growing just "n" pages
|
| + ask = n;
|
| + ptr = TCMalloc_SystemAlloc(ask << kPageShift, &actual_size, kPageSize);
|
| + }
|
| + if (ptr == NULL) return false;
|
| + }
|
| + ask = actual_size >> kPageShift;
|
| + RecordGrowth(ask << kPageShift);
|
| +
|
| + uint64_t old_system_bytes = system_bytes_;
|
| + system_bytes_ += (ask << kPageShift);
|
| + const PageID p = reinterpret_cast<uintptr_t>(ptr) >> kPageShift;
|
| + ASSERT(p > 0);
|
| +
|
| + // If we have already a lot of pages allocated, just pre allocate a bunch of
|
| + // memory for the page map. This prevents fragmentation by pagemap metadata
|
| + // when a program keeps allocating and freeing large blocks.
|
| +
|
| + if (old_system_bytes < kPageMapBigAllocationThreshold
|
| + && system_bytes_ >= kPageMapBigAllocationThreshold) {
|
| + pagemap_.PreallocateMoreMemory();
|
| + }
|
| +
|
| + // Make sure pagemap_ has entries for all of the new pages.
|
| + // Plus ensure one before and one after so coalescing code
|
| + // does not need bounds-checking.
|
| + if (pagemap_.Ensure(p-1, ask+2)) {
|
| + // Pretend the new area is allocated and then Delete() it to
|
| + // cause any necessary coalescing to occur.
|
| + //
|
| + // We do not adjust free_pages_ here since Delete() will do it for us.
|
| + Span* span = NewSpan(p, ask);
|
| + RecordSpan(span);
|
| + Delete(span);
|
| + ASSERT(Check());
|
| + return true;
|
| + } else {
|
| + // We could not allocate memory within "pagemap_"
|
| + // TODO: Once we can return memory to the system, return the new span
|
| + return false;
|
| + }
|
| +}
|
| +
|
| +bool PageHeap::Check() {
|
| + ASSERT(free_[0].normal.next == &free_[0].normal);
|
| + ASSERT(free_[0].returned.next == &free_[0].returned);
|
| + return true;
|
| +}
|
| +
|
| +bool PageHeap::CheckExpensive() {
|
| + bool result = Check();
|
| + CheckList(&large_.normal, kMaxPages, 1000000000, Span::ON_NORMAL_FREELIST);
|
| + CheckList(&large_.returned, kMaxPages, 1000000000, Span::ON_RETURNED_FREELIST);
|
| + for (Length s = 1; s < kMaxPages; s++) {
|
| + CheckList(&free_[s].normal, s, s, Span::ON_NORMAL_FREELIST);
|
| + CheckList(&free_[s].returned, s, s, Span::ON_RETURNED_FREELIST);
|
| + }
|
| + return result;
|
| +}
|
| +
|
| +bool PageHeap::CheckList(Span* list, Length min_pages, Length max_pages,
|
| + int freelist) {
|
| + for (Span* s = list->next; s != list; s = s->next) {
|
| + CHECK_CONDITION(s->location == freelist); // NORMAL or RETURNED
|
| + CHECK_CONDITION(s->length >= min_pages);
|
| + CHECK_CONDITION(s->length <= max_pages);
|
| + CHECK_CONDITION(GetDescriptor(s->start) == s);
|
| + CHECK_CONDITION(GetDescriptor(s->start+s->length-1) == s);
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +static void ReleaseFreeList(Span* list, Span* returned) {
|
| + // Walk backwards through list so that when we push these
|
| + // spans on the "returned" list, we preserve the order.
|
| + while (!DLL_IsEmpty(list)) {
|
| + Span* s = list->prev;
|
| + DLL_Remove(s);
|
| + DLL_Prepend(returned, s);
|
| + ASSERT(s->location == Span::ON_NORMAL_FREELIST);
|
| + s->location = Span::ON_RETURNED_FREELIST;
|
| + TCMalloc_SystemRelease(reinterpret_cast<void*>(s->start << kPageShift),
|
| + static_cast<size_t>(s->length << kPageShift));
|
| + }
|
| +}
|
| +
|
| +void PageHeap::ReleaseFreePages() {
|
| + for (Length s = 0; s < kMaxPages; s++) {
|
| + ReleaseFreeList(&free_[s].normal, &free_[s].returned);
|
| + }
|
| + ReleaseFreeList(&large_.normal, &large_.returned);
|
| + ASSERT(Check());
|
| +}
|
| +
|
| +} // namespace tcmalloc
|
|
|