| // 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> |
| #ifdef HAVE_INTTYPES_H |
| #include <inttypes.h> // for PRIuPTR |
| #endif |
| #include <gperftools/malloc_extension.h> // for MallocRange, etc |
| #include "base/basictypes.h" |
| #include "base/commandlineflags.h" |
| #include "internal_logging.h" // for ASSERT, TCMalloc_Printer, etc |
| #include "page_heap_allocator.h" // for PageHeapAllocator |
| #include "static_vars.h" // for Static |
| #include "system-alloc.h" // for TCMalloc_SystemAlloc, etc |
| |
| 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), |
| scavenge_counter_(0), |
| // Start scavenging at kMaxPages list |
| release_index_(kMaxPages) { |
| 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::SearchFreeAndLargeLists(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); |
| } |
| } |
| // No luck in free lists, our last chance is in a larger class. |
| return AllocLarge(n); // May be NULL |
| } |
| |
| Span* PageHeap::New(Length n) { |
| ASSERT(Check()); |
| ASSERT(n > 0); |
| |
| Span* result = SearchFreeAndLargeLists(n); |
| if (result != NULL) |
| return result; |
| |
| // Grow the heap and try again. |
| if (!GrowHeap(n)) { |
| ASSERT(stats_.unmapped_bytes+ stats_.committed_bytes==stats_.system_bytes); |
| ASSERT(Check()); |
| return NULL; |
| } |
| return SearchFreeAndLargeLists(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; |
| } |
| |
| void PageHeap::CommitSpan(Span* span) { |
| TCMalloc_SystemCommit(reinterpret_cast<void*>(span->start << kPageShift), |
| static_cast<size_t>(span->length << kPageShift)); |
| stats_.committed_bytes += span->length << kPageShift; |
| } |
| |
| void PageHeap::DecommitSpan(Span* span) { |
| TCMalloc_SystemRelease(reinterpret_cast<void*>(span->start << kPageShift), |
| static_cast<size_t>(span->length << kPageShift)); |
| stats_.committed_bytes -= span->length << kPageShift; |
| } |
| |
| Span* PageHeap::Carve(Span* span, Length n) { |
| ASSERT(n > 0); |
| ASSERT(span->location != Span::IN_USE); |
| const int old_location = span->location; |
| RemoveFromFreeList(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); |
| |
| // The previous span of |leftover| was just splitted -- no need to |
| // coalesce them. The next span of |leftover| was not previously coalesced |
| // with |span|, i.e. is NULL or has got location other than |old_location|. |
| const PageID p = leftover->start; |
| const Length len = leftover->length; |
| Span* next = GetDescriptor(p+len); |
| ASSERT (next == NULL || |
| next->location == Span::IN_USE || |
| next->location != leftover->location); |
| |
| PrependToFreeList(leftover); // Skip coalescing - no candidates possible |
| span->length = n; |
| pagemap_.set(span->start + n - 1, span); |
| } |
| ASSERT(Check()); |
| if (old_location == Span::ON_RETURNED_FREELIST) { |
| // We need to recommit this address space. |
| CommitSpan(span); |
| } |
| ASSERT(span->location == Span::IN_USE); |
| ASSERT(span->length == n); |
| ASSERT(stats_.unmapped_bytes+ stats_.committed_bytes==stats_.system_bytes); |
| 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); |
| const Length n = span->length; |
| span->sizeclass = 0; |
| span->sample = 0; |
| span->location = Span::ON_NORMAL_FREELIST; |
| Event(span, 'D', span->length); |
| MergeIntoFreeList(span); // Coalesces if possible |
| IncrementalScavenge(n); |
| ASSERT(stats_.unmapped_bytes+ stats_.committed_bytes==stats_.system_bytes); |
| ASSERT(Check()); |
| } |
| |
| void PageHeap::MergeIntoFreeList(Span* span) { |
| ASSERT(span->location != Span::IN_USE); |
| |
| // 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 adjacent spans we merge into "span" may come out of a |
| // "normal" (committed) list, and cleanly merge with our IN_USE span, which |
| // is implicitly committed. If the adjacents spans are on the "returned" |
| // (decommitted) list, then we must get both spans into the same state before |
| // or after we coalesce them. The current code always decomits. This is |
| // achieved by blindly decommitting the entire coalesced region, which may |
| // include any combination of committed and decommitted spans, at the end of |
| // the method. |
| |
| // TODO(jar): "Always decommit" causes some extra calls to commit when we are |
| // called in GrowHeap() during an allocation :-/. We need to eval the cost of |
| // that oscillation, and possibly do something to reduce it. |
| |
| // TODO(jar): We need a better strategy for deciding to commit, or decommit, |
| // based on memory usage and free heap sizes. |
| |
| 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; |
| if (prev->location == Span::ON_RETURNED_FREELIST) { |
| // We're about to put the merge span into the returned freelist and call |
| // DecommitSpan() on it, which will mark the entire span including this |
| // one as released and decrease stats_.committed_bytes by the size of the |
| // merged span. To make the math work out we temporarily increase the |
| // stats_.committed_bytes amount. |
| stats_.committed_bytes += prev->length << kPageShift; |
| } |
| RemoveFromFreeList(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; |
| if (next->location == Span::ON_RETURNED_FREELIST) { |
| // See the comment below 'if (prev->location ...' for explanation. |
| stats_.committed_bytes += next->length << kPageShift; |
| } |
| RemoveFromFreeList(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; |
| DecommitSpan(span); |
| PrependToFreeList(span); |
| } |
| |
| void PageHeap::PrependToFreeList(Span* span) { |
| ASSERT(span->location != Span::IN_USE); |
| SpanList* list = (span->length < kMaxPages) ? &free_[span->length] : &large_; |
| if (span->location == Span::ON_NORMAL_FREELIST) { |
| stats_.free_bytes += (span->length << kPageShift); |
| DLL_Prepend(&list->normal, span); |
| } else { |
| stats_.unmapped_bytes += (span->length << kPageShift); |
| DLL_Prepend(&list->returned, span); |
| } |
| } |
| |
| void PageHeap::RemoveFromFreeList(Span* span) { |
| ASSERT(span->location != Span::IN_USE); |
| if (span->location == Span::ON_NORMAL_FREELIST) { |
| stats_.free_bytes -= (span->length << kPageShift); |
| } else { |
| stats_.unmapped_bytes -= (span->length << kPageShift); |
| } |
| DLL_Remove(span); |
| } |
| |
| 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 |
| |
| const double rate = FLAGS_tcmalloc_release_rate; |
| if (rate <= 1e-6) { |
| // Tiny release rate means that releasing is disabled. |
| scavenge_counter_ = kDefaultReleaseDelay; |
| return; |
| } |
| |
| Length released_pages = ReleaseAtLeastNPages(1); |
| |
| if (released_pages == 0) { |
| // Nothing to scavenge, delay for a while. |
| scavenge_counter_ = kDefaultReleaseDelay; |
| } else { |
| // 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>(released_pages); |
| if (wait > kMaxReleaseDelay) { |
| // Avoid overflow and bound to reasonable range. |
| wait = kMaxReleaseDelay; |
| } |
| scavenge_counter_ = static_cast<int64_t>(wait); |
| } |
| } |
| |
| Length PageHeap::ReleaseLastNormalSpan(SpanList* slist) { |
| Span* s = slist->normal.prev; |
| ASSERT(s->location == Span::ON_NORMAL_FREELIST); |
| RemoveFromFreeList(s); |
| const Length n = s->length; |
| TCMalloc_SystemRelease(reinterpret_cast<void*>(s->start << kPageShift), |
| static_cast<size_t>(s->length << kPageShift)); |
| s->location = Span::ON_RETURNED_FREELIST; |
| MergeIntoFreeList(s); // Coalesces if possible. |
| return n; |
| } |
| |
| Length PageHeap::ReleaseAtLeastNPages(Length num_pages) { |
| Length released_pages = 0; |
| Length prev_released_pages = -1; |
| |
| // Round robin through the lists of free spans, releasing the last |
| // span in each list. Stop after releasing at least num_pages. |
| while (released_pages < num_pages) { |
| if (released_pages == prev_released_pages) { |
| // Last iteration of while loop made no progress. |
| break; |
| } |
| prev_released_pages = released_pages; |
| |
| for (int i = 0; i < kMaxPages+1 && released_pages < num_pages; |
| i++, release_index_++) { |
| if (release_index_ > kMaxPages) release_index_ = 0; |
| SpanList* slist = (release_index_ == kMaxPages) ? |
| &large_ : &free_[release_index_]; |
| if (!DLL_IsEmpty(&slist->normal)) { |
| Length released_len = ReleaseLastNormalSpan(slist); |
| released_pages += released_len; |
| } |
| } |
| } |
| return released_pages; |
| } |
| |
| 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); |
| } |
| } |
| |
| void PageHeap::GetSmallSpanStats(SmallSpanStats* result) { |
| for (int s = 0; s < kMaxPages; s++) { |
| result->normal_length[s] = DLL_Length(&free_[s].normal); |
| result->returned_length[s] = DLL_Length(&free_[s].returned); |
| } |
| } |
| |
| void PageHeap::GetLargeSpanStats(LargeSpanStats* result) { |
| result->spans = 0; |
| result->normal_pages = 0; |
| result->returned_pages = 0; |
| for (Span* s = large_.normal.next; s != &large_.normal; s = s->next) { |
| result->normal_pages += s->length;; |
| result->spans++; |
| } |
| for (Span* s = large_.returned.next; s != &large_.returned; s = s->next) { |
| result->returned_pages += s->length; |
| result->spans++; |
| } |
| } |
| |
| bool PageHeap::GetNextRange(PageID start, base::MallocRange* r) { |
| Span* span = reinterpret_cast<Span*>(pagemap_.Next(start)); |
| if (span == NULL) { |
| return false; |
| } |
| r->address = span->start << kPageShift; |
| r->length = span->length << kPageShift; |
| r->fraction = 0; |
| switch (span->location) { |
| case Span::IN_USE: |
| r->type = base::MallocRange::INUSE; |
| r->fraction = 1; |
| if (span->sizeclass > 0) { |
| // Only some of the objects in this span may be in use. |
| const size_t osize = Static::sizemap()->class_to_size(span->sizeclass); |
| r->fraction = (1.0 * osize * span->refcount) / r->length; |
| } |
| break; |
| case Span::ON_NORMAL_FREELIST: |
| r->type = base::MallocRange::FREE; |
| break; |
| case Span::ON_RETURNED_FREELIST: |
| r->type = base::MallocRange::UNMAPPED; |
| break; |
| default: |
| r->type = base::MallocRange::UNKNOWN; |
| break; |
| } |
| return true; |
| } |
| |
| 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 = stats_.system_bytes; |
| stats_.system_bytes += (ask << kPageShift); |
| stats_.committed_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 |
| && stats_.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. |
| Span* span = NewSpan(p, ask); |
| RecordSpan(span); |
| Delete(span); |
| ASSERT(stats_.unmapped_bytes+ stats_.committed_bytes==stats_.system_bytes); |
| 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; |
| } |
| |
| } // namespace tcmalloc |