Files
sdk/runtime/vm/pages.h
T
Zach Anderson 0bec11a4f2 [vm] Control growth of external allocations
Previously, GC would only be triggered by external allocations on
reaching a limit indicated by a flag. This limit was too large
and not set thoughtfully by anyone.

This CL triggers GC when external allocations in old gen grow
larger than some limit based on results of previous GC, similar
to how Dart heap growth triggers GC. GC is triggered for new gen
when external allocations reach 4x the current new gen
capacity.

FL-61

Change-Id: I1d4da467bc2aab64058f25296aefdd73eebc38e6
Reviewed-on: https://dart-review.googlesource.com/58100
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Zach Anderson <zra@google.com>
2018-06-04 17:24:18 +00:00

464 lines
14 KiB
C++

// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#ifndef RUNTIME_VM_PAGES_H_
#define RUNTIME_VM_PAGES_H_
#include "vm/freelist.h"
#include "vm/globals.h"
#include "vm/lockers.h"
#include "vm/ring_buffer.h"
#include "vm/spaces.h"
#include "vm/thread.h"
#include "vm/virtual_memory.h"
namespace dart {
DECLARE_FLAG(bool, log_code_drop);
DECLARE_FLAG(bool, always_drop_code);
DECLARE_FLAG(bool, write_protect_code);
// Forward declarations.
class Heap;
class JSONObject;
class ObjectPointerVisitor;
class ObjectSet;
class ForwardingPage;
// TODO(iposva): Determine heap sizes and tune the page size accordingly.
static const intptr_t kPageSize = 256 * KB;
static const intptr_t kPageSizeInWords = kPageSize / kWordSize;
static const intptr_t kPageMask = ~(kPageSize - 1);
// A page containing old generation objects.
class HeapPage {
public:
enum PageType { kData = 0, kExecutable, kNumPageTypes };
HeapPage* next() const { return next_; }
void set_next(HeapPage* next) { next_ = next; }
bool Contains(uword addr) { return memory_->Contains(addr); }
uword object_start() const { return memory_->start() + ObjectStartOffset(); }
uword object_end() const { return object_end_; }
uword used_in_bytes() const { return used_in_bytes_; }
void set_used_in_bytes(uword value) {
ASSERT(Utils::IsAligned(value, kObjectAlignment));
used_in_bytes_ = value;
}
ForwardingPage* forwarding_page() const { return forwarding_page_; }
ForwardingPage* AllocateForwardingPage();
void FreeForwardingPage();
PageType type() const { return type_; }
bool is_image_page() const { return !memory_->vm_owns_region(); }
void VisitObjects(ObjectVisitor* visitor) const;
void VisitObjectPointers(ObjectPointerVisitor* visitor) const;
RawObject* FindObject(FindObjectVisitor* visitor) const;
void WriteProtect(bool read_only);
static intptr_t ObjectStartOffset() {
return Utils::RoundUp(sizeof(HeapPage), OS::kMaxPreferredCodeAlignment);
}
// Warning: This does not work for objects on image pages because image pages
// are not aligned.
static HeapPage* Of(RawObject* obj) {
ASSERT(obj->IsHeapObject());
ASSERT(obj->IsOldObject());
return reinterpret_cast<HeapPage*>(reinterpret_cast<uword>(obj) &
kPageMask);
}
static HeapPage* Of(uintptr_t addr) {
return reinterpret_cast<HeapPage*>(addr & kPageMask);
}
private:
void set_object_end(uword value) {
ASSERT((value & kObjectAlignmentMask) == kOldObjectAlignmentOffset);
object_end_ = value;
}
// Returns NULL on OOM.
static HeapPage* Allocate(intptr_t size_in_words,
PageType type,
const char* name);
// Deallocate the virtual memory backing this page. The page pointer to this
// page becomes immediately inaccessible.
void Deallocate();
VirtualMemory* memory_;
HeapPage* next_;
uword object_end_;
uword used_in_bytes_;
ForwardingPage* forwarding_page_;
PageType type_;
friend class PageSpace;
friend class GCCompactor;
DISALLOW_ALLOCATION();
DISALLOW_IMPLICIT_CONSTRUCTORS(HeapPage);
};
// The history holds the timing information of the last garbage collection
// runs.
class PageSpaceGarbageCollectionHistory {
public:
PageSpaceGarbageCollectionHistory() {}
~PageSpaceGarbageCollectionHistory() {}
void AddGarbageCollectionTime(int64_t start, int64_t end);
int GarbageCollectionTimeFraction();
bool IsEmpty() const { return history_.Size() == 0; }
private:
struct Entry {
int64_t start;
int64_t end;
};
static const intptr_t kHistoryLength = 4;
RingBuffer<Entry, kHistoryLength> history_;
DISALLOW_ALLOCATION();
DISALLOW_COPY_AND_ASSIGN(PageSpaceGarbageCollectionHistory);
};
// PageSpaceController controls the heap size.
class PageSpaceController {
public:
// The heap is passed in for recording stats only. The controller does not
// invoke GC by itself.
PageSpaceController(Heap* heap,
int heap_growth_ratio,
int heap_growth_max,
int garbage_collection_time_ratio);
~PageSpaceController();
// Returns whether growing to 'after' should trigger a GC.
// This method can be called before allocation (e.g., pretenuring) or after
// (e.g., promotion), as it does not change the state of the controller.
bool NeedsGarbageCollection(SpaceUsage after) const;
bool NeedsExternalCollection(SpaceUsage after) const;
// Returns whether an idle GC is worthwhile.
bool NeedsIdleGarbageCollection(SpaceUsage current) const;
// Should be called after each collection to update the controller state.
void EvaluateGarbageCollection(SpaceUsage before,
SpaceUsage after,
int64_t start,
int64_t end);
int64_t last_code_collection_in_us() { return last_code_collection_in_us_; }
void set_last_code_collection_in_us(int64_t t) {
last_code_collection_in_us_ = t;
}
void set_last_usage(SpaceUsage current) { last_usage_ = current; }
void Enable() { is_enabled_ = true; }
void Disable() { is_enabled_ = false; }
bool is_enabled() { return is_enabled_; }
private:
Heap* heap_;
bool is_enabled_;
// Usage after last evaluated GC or last enabled.
SpaceUsage last_usage_;
// Pages of capacity growth allowed before next GC is advised.
intptr_t grow_heap_;
// Pages of external growth allowed before next GC is advised.
intptr_t grow_external_;
// If the garbage collector was not able to free more than heap_growth_ratio_
// memory, then the heap is grown. Otherwise garbage collection is performed.
int heap_growth_ratio_;
// The desired percent of heap in-use after a garbage collection.
// Equivalent to \frac{100-heap_growth_ratio_}{100}.
double desired_utilization_;
// Max number of pages we grow.
int heap_growth_max_;
// If the relative GC time goes above garbage_collection_time_ratio_ %,
// we grow the heap more aggressively.
int garbage_collection_time_ratio_;
// The time in microseconds of the last time we tried to collect unused
// code.
int64_t last_code_collection_in_us_;
// We start considering idle mark-sweeps when old space crosses this size.
intptr_t idle_gc_threshold_in_words_;
PageSpaceGarbageCollectionHistory history_;
DISALLOW_IMPLICIT_CONSTRUCTORS(PageSpaceController);
};
class PageSpace {
public:
enum GrowthPolicy { kControlGrowth, kForceGrowth };
PageSpace(Heap* heap, intptr_t max_capacity_in_words);
~PageSpace();
uword TryAllocate(intptr_t size,
HeapPage::PageType type = HeapPage::kData,
GrowthPolicy growth_policy = kControlGrowth) {
bool is_protected =
(type == HeapPage::kExecutable) && FLAG_write_protect_code;
bool is_locked = false;
return TryAllocateInternal(size, type, growth_policy, is_protected,
is_locked);
}
bool NeedsGarbageCollection() const {
return page_space_controller_.NeedsGarbageCollection(usage_);
}
int64_t UsedInWords() const { return usage_.used_in_words; }
int64_t CapacityInWords() const {
MutexLocker ml(pages_lock_);
return usage_.capacity_in_words;
}
void IncreaseCapacityInWords(intptr_t increase_in_words) {
MutexLocker ml(pages_lock_);
IncreaseCapacityInWordsLocked(increase_in_words);
}
void IncreaseCapacityInWordsLocked(intptr_t increase_in_words) {
DEBUG_ASSERT(pages_lock_->IsOwnedByCurrentThread());
usage_.capacity_in_words += increase_in_words;
UpdateMaxCapacityLocked();
}
void UpdateMaxCapacityLocked();
void UpdateMaxUsed();
int64_t ExternalInWords() const { return usage_.external_in_words; }
SpaceUsage GetCurrentUsage() const {
MutexLocker ml(pages_lock_);
return usage_;
}
bool Contains(uword addr) const;
bool Contains(uword addr, HeapPage::PageType type) const;
bool DataContains(uword addr) const;
bool IsValidAddress(uword addr) const { return Contains(addr); }
void VisitObjects(ObjectVisitor* visitor) const;
void VisitObjectsNoImagePages(ObjectVisitor* visitor) const;
void VisitObjectsImagePages(ObjectVisitor* visitor) const;
void VisitObjectPointers(ObjectPointerVisitor* visitor) const;
RawObject* FindObject(FindObjectVisitor* visitor,
HeapPage::PageType type) const;
// Checks if enough time has elapsed since the last attempt to collect
// code.
bool ShouldCollectCode();
// Collect the garbage in the page space using mark-sweep or mark-compact.
void CollectGarbage(bool compact);
void AddRegionsToObjectSet(ObjectSet* set) const;
void InitGrowthControl() {
page_space_controller_.set_last_usage(usage_);
page_space_controller_.Enable();
}
void SetGrowthControlState(bool state) {
if (state) {
page_space_controller_.Enable();
} else {
page_space_controller_.Disable();
}
}
bool GrowthControlState() { return page_space_controller_.is_enabled(); }
// Note: Code pages are made executable/non-executable when 'read_only' is
// true/false, respectively.
void WriteProtect(bool read_only);
void WriteProtectCode(bool read_only);
bool ShouldPerformIdleMarkSweep(int64_t deadline);
bool ShouldPerformIdleMarkCompact(int64_t deadline);
void AddGCTime(int64_t micros) { gc_time_micros_ += micros; }
int64_t gc_time_micros() const { return gc_time_micros_; }
void IncrementCollections() { collections_++; }
intptr_t collections() const { return collections_; }
#ifndef PRODUCT
void PrintToJSONObject(JSONObject* object) const;
void PrintHeapMapToJSONStream(Isolate* isolate, JSONStream* stream) const;
#endif // PRODUCT
void AllocateExternal(intptr_t cid, intptr_t size);
void FreeExternal(intptr_t size);
// Bulk data allocation.
void AcquireDataLock();
void ReleaseDataLock();
uword TryAllocateDataLocked(intptr_t size, GrowthPolicy growth_policy) {
bool is_protected = false;
bool is_locked = true;
return TryAllocateInternal(size, HeapPage::kData, growth_policy,
is_protected, is_locked);
}
Monitor* tasks_lock() const { return tasks_lock_; }
intptr_t tasks() const { return tasks_; }
void set_tasks(intptr_t val) {
ASSERT(val >= 0);
tasks_ = val;
}
// Attempt to allocate from bump block rather than normal freelist.
uword TryAllocateDataBump(intptr_t size, GrowthPolicy growth_policy);
uword TryAllocateDataBumpLocked(intptr_t size, GrowthPolicy growth_policy);
// Prefer small freelist blocks, then chip away at the bump block.
uword TryAllocatePromoLocked(intptr_t size, GrowthPolicy growth_policy);
void SetupImagePage(void* pointer, uword size, bool is_executable);
// Return any bump allocation block to the freelist.
void AbandonBumpAllocation();
private:
// Ids for time and data records in Heap::GCStats.
enum {
// Time
kConcurrentSweep = 0,
kSafePoint = 1,
kMarkObjects = 2,
kResetFreeLists = 3,
kSweepPages = 4,
kSweepLargePages = 5,
// Data
kGarbageRatio = 0,
kGCTimeFraction = 1,
kPageGrowth = 2,
kAllowedGrowth = 3
};
static const intptr_t kAllocatablePageSize = 64 * KB;
uword TryAllocateInternal(intptr_t size,
HeapPage::PageType type,
GrowthPolicy growth_policy,
bool is_protected,
bool is_locked);
uword TryAllocateInFreshPage(intptr_t size,
HeapPage::PageType type,
GrowthPolicy growth_policy,
bool is_locked);
uword TryAllocateDataBumpInternal(intptr_t size,
GrowthPolicy growth_policy,
bool is_locked);
// Makes bump block walkable; do not call concurrently with mutator.
void MakeIterable() const;
HeapPage* AllocatePage(HeapPage::PageType type);
void FreePage(HeapPage* page, HeapPage* previous_page);
HeapPage* AllocateLargePage(intptr_t size, HeapPage::PageType type);
void TruncateLargePage(HeapPage* page, intptr_t new_object_size_in_bytes);
void FreeLargePage(HeapPage* page, HeapPage* previous_page);
void FreePages(HeapPage* pages);
void BlockingSweep();
void ConcurrentSweep(Isolate* isolate);
void Compact(Thread* thread);
static intptr_t LargePageSizeInWordsFor(intptr_t size);
bool CanIncreaseCapacityInWords(intptr_t increase_in_words) {
if (max_capacity_in_words_ == 0) {
// Unlimited.
return true;
}
// TODO(issue 27413): Make the check against capacity and the bump
// of capacity atomic so that CapacityInWords does not exceed
// max_capacity_in_words_.
intptr_t free_capacity_in_words =
(max_capacity_in_words_ - CapacityInWords());
return ((free_capacity_in_words > 0) &&
(increase_in_words <= free_capacity_in_words));
}
FreeList freelist_[HeapPage::kNumPageTypes];
Heap* heap_;
// Use ExclusivePageIterator for safe access to these.
Mutex* pages_lock_;
HeapPage* pages_;
HeapPage* pages_tail_;
HeapPage* exec_pages_;
HeapPage* exec_pages_tail_;
HeapPage* large_pages_;
HeapPage* image_pages_;
// A block of memory in a data page, managed by bump allocation. The remainder
// is kept formatted as a FreeListElement, but is not in any freelist.
uword bump_top_;
uword bump_end_;
// Various sizes being tracked for this generation.
intptr_t max_capacity_in_words_;
// NOTE: The capacity component of usage_ is updated by the concurrent
// sweeper. Use (Increase)CapacityInWords(Locked) for thread-safe access.
SpaceUsage usage_;
// Keep track of running MarkSweep tasks.
Monitor* tasks_lock_;
intptr_t tasks_;
#if defined(DEBUG)
Thread* iterating_thread_;
#endif
PageSpaceController page_space_controller_;
int64_t gc_time_micros_;
intptr_t collections_;
intptr_t mark_words_per_micro_;
friend class ExclusivePageIterator;
friend class ExclusiveCodePageIterator;
friend class ExclusiveLargePageIterator;
friend class HeapIterationScope;
friend class PageSpaceController;
friend class SweeperTask;
friend class GCCompactor;
friend class CompactorTask;
DISALLOW_IMPLICIT_CONSTRUCTORS(PageSpace);
};
} // namespace dart
#endif // RUNTIME_VM_PAGES_H_