Finalizer queue (#801)

This commit is contained in:
Nikolay Igotti
2017-08-21 18:57:46 +03:00
committed by GitHub
parent 30c1b4d6d5
commit 9402a9c6a2
2 changed files with 120 additions and 45 deletions
+75 -41
View File
@@ -18,8 +18,6 @@
#include <stdio.h> #include <stdio.h>
#include <cstddef> // for offsetof #include <cstddef> // for offsetof
#include <unordered_set>
#include <vector>
#include "Alloc.h" #include "Alloc.h"
#include "Assert.h" #include "Assert.h"
@@ -54,6 +52,8 @@ constexpr container_size_t kObjectAlignment = 8;
// Collection threshold default (collect after having so many elements in the // Collection threshold default (collect after having so many elements in the
// release candidates set). Better be a prime number. // release candidates set). Better be a prime number.
constexpr size_t kGcThreshold = 9341; constexpr size_t kGcThreshold = 9341;
typedef KStdDeque<ContainerHeader*> ContainerHeaderDeque;
#endif #endif
#if TRACE_MEMORY || USE_GC #if TRACE_MEMORY || USE_GC
@@ -74,6 +74,9 @@ struct MemoryState {
#endif #endif
#if USE_GC #if USE_GC
// Finalizer queue.
ContainerHeaderDeque* finalizerQueue;
// Set of references to release. // Set of references to release.
ContainerHeaderSet* toFree; ContainerHeaderSet* toFree;
// How many GC suspend requests happened. // How many GC suspend requests happened.
@@ -128,6 +131,34 @@ inline ObjHeader** asArenaSlot(ObjHeader** slot) {
reinterpret_cast<uintptr_t>(slot) & ~ARENA_BIT); reinterpret_cast<uintptr_t>(slot) & ~ARENA_BIT);
} }
#if USE_GC
inline void processFinalizerQueue(MemoryState* state) {
// TODO: reuse elements of finalizer queue for new allocations.
while (!state->finalizerQueue->empty()) {
auto container = memoryState->finalizerQueue->back();
state->finalizerQueue->pop_back();
konanFreeMemory(container);
state->allocCount--;
}
}
#endif
inline void scheduleDestroyContainer(
MemoryState* state, ContainerHeader* container) {
#if USE_GC
state->finalizerQueue->push_front(container);
// We cannot clean finalizer queue while in GC.
if (!state->gcInProgress && state->finalizerQueue->size() > 256) {
processFinalizerQueue(state);
}
#else
state->allocCount--;
konanFreeMemory(header);
#endif
}
#if USE_GC #if USE_GC
inline uint32_t hashOf(ContainerHeader* container) { inline uint32_t hashOf(ContainerHeader* container) {
@@ -219,7 +250,7 @@ inline void initThreshold(MemoryState* state, uint32_t gcThreshold) {
ContainerHeaderList collectMutableReferred(ContainerHeader* header) { ContainerHeaderList collectMutableReferred(ContainerHeader* header) {
ContainerHeaderList result; ContainerHeaderList result;
ObjHeader* obj = reinterpret_cast<ObjHeader*>(header + 1); ObjHeader* obj = reinterpret_cast<ObjHeader*>(header + 1);
for (int object = 0; object < header->objectCount_; object++) { for (int object = 0; object < header->objectCount(); object++) {
const TypeInfo* typeInfo = obj->type_info(); const TypeInfo* typeInfo = obj->type_info();
// TODO: generalize iteration over all references. // TODO: generalize iteration over all references.
for (int index = 0; index < typeInfo->objOffsetsCount_; index++) { for (int index = 0; index < typeInfo->objOffsetsCount_; index++) {
@@ -273,8 +304,8 @@ void phase1(ContainerHeader* header) {
header->refCount_ |= CONTAINER_TAG_SEEN; header->refCount_ |= CONTAINER_TAG_SEEN;
auto containers = collectMutableReferred(header); auto containers = collectMutableReferred(header);
for (auto container : containers) { for (auto container : containers) {
container->refCount_ -= CONTAINER_TAG_INCREMENT; container->decRefCount();
phase1(container); phase1(container);
} }
} }
@@ -297,29 +328,27 @@ void phase3(ContainerHeader* header) {
header->refCount_ |= CONTAINER_TAG_SEEN; header->refCount_ |= CONTAINER_TAG_SEEN;
auto containers = collectMutableReferred(header); auto containers = collectMutableReferred(header);
for (auto container : containers) { for (auto container : containers) {
container->refCount_ += CONTAINER_TAG_INCREMENT; container->incRefCount();
phase3(container); phase3(container);
} }
} }
void phase4(ContainerHeader* header, ContainerHeaderSet* toRemove) { void phase4(MemoryState* state, ContainerHeader* header) {
auto refCount = header->refCount_ >> CONTAINER_TAG_SHIFT; auto refCount = header->refCount_ >> CONTAINER_TAG_SHIFT;
bool seen = (refCount > 0 && (header->refCount_ & CONTAINER_TAG_SEEN) == 0) || bool seen = (refCount > 0 && (header->refCount_ & CONTAINER_TAG_SEEN) == 0) ||
(refCount == 0 && (header->refCount_ & CONTAINER_TAG_SEEN) != 0); (refCount == 0 && (header->refCount_ & CONTAINER_TAG_SEEN) != 0);
if (seen) return; if (seen) return;
// Add to toRemove set. // Add to finalize queue and update seen bit.
if (refCount == 0) if (refCount == 0) {
toRemove->insert(header); scheduleDestroyContainer(state, header);
// Update seen bit.
if (refCount == 0)
header->refCount_ |= CONTAINER_TAG_SEEN; header->refCount_ |= CONTAINER_TAG_SEEN;
else } else {
header->refCount_ &= ~CONTAINER_TAG_SEEN; header->refCount_ &= ~CONTAINER_TAG_SEEN;
}
auto containers = collectMutableReferred(header); auto containers = collectMutableReferred(header);
for (auto container : containers) { for (auto container : containers) {
phase4(container, toRemove); phase4(state, container);
} }
} }
@@ -340,13 +369,17 @@ inline ArenaContainer* initedArena(ObjHeader** auxSlot) {
} // namespace } // namespace
ContainerHeader* AllocContainer(size_t size) { ContainerHeader* AllocContainer(size_t size) {
auto state = memoryState;
#if USE_GC
// TODO: try to reuse elements of finalizer queue for new allocations, question
// is how to get actual size of container.
#endif
ContainerHeader* result = konanConstructSizedInstance<ContainerHeader>(size); ContainerHeader* result = konanConstructSizedInstance<ContainerHeader>(size);
#if TRACE_MEMORY #if TRACE_MEMORY
fprintf(stderr, ">>> alloc %d -> %p\n", static_cast<int>(size), result); fprintf(stderr, ">>> alloc %d -> %p\n", static_cast<int>(size), result);
memoryState->containers->insert(result); state->containers->insert(result);
#endif #endif
// TODO: atomic increment in concurrent case. state->allocCount++;
memoryState->allocCount++;
return result; return result;
} }
@@ -377,20 +410,21 @@ void DeinitInstanceBody(const TypeInfo* typeInfo, void* body) {
void FreeContainer(ContainerHeader* header) { void FreeContainer(ContainerHeader* header) {
RuntimeAssert(!isPermanent(header), "this kind of container shalln't be freed"); RuntimeAssert(!isPermanent(header), "this kind of container shalln't be freed");
auto state = memoryState;
#if TRACE_MEMORY #if TRACE_MEMORY
if (isFreeable(header)) { if (isFreeable(header)) {
fprintf(stderr, "<<< free %p\n", header); fprintf(stderr, "<<< free %p\n", header);
memoryState->containers->erase(header); state->containers->erase(header);
} }
#endif #endif
#if USE_GC #if USE_GC
removeFreeable(memoryState, header); removeFreeable(state, header);
#endif #endif
// Now let's clean all object's fields in this container. // Now let's clean all object's fields in this container.
ObjHeader* obj = reinterpret_cast<ObjHeader*>(header + 1); ObjHeader* obj = reinterpret_cast<ObjHeader*>(header + 1);
for (int index = 0; index < header->objectCount_; index++) { for (int index = 0; index < header->objectCount(); index++) {
runDeallocationHooks(obj); runDeallocationHooks(obj);
const TypeInfo* typeInfo = obj->type_info(); const TypeInfo* typeInfo = obj->type_info();
@@ -408,32 +442,29 @@ void FreeContainer(ContainerHeader* header) {
// And release underlying memory. // And release underlying memory.
if (isFreeable(header)) { if (isFreeable(header)) {
memoryState->allocCount--; scheduleDestroyContainer(state, header);
konanFreeMemory(header);
} }
} }
#if USE_GC #if USE_GC
void FreeContainerNoRef(ContainerHeader* header) { void FreeContainerNoRef(MemoryState* state, ContainerHeader* header) {
RuntimeAssert(isFreeable(header), "this kind of container shalln't be freed"); RuntimeAssert(isFreeable(header), "this kind of container shalln't be freed");
#if TRACE_MEMORY #if TRACE_MEMORY
fprintf(stderr, "<<< free %p\n", header); fprintf(stderr, "<<< free %p\n", header);
memoryState->containers->erase(header); state->containers->erase(header);
#endif #endif
#if USE_GC #if USE_GC
removeFreeable(memoryState, header); removeFreeable(state, header);
#endif #endif
ObjHeader* obj = reinterpret_cast<ObjHeader*>(header + 1); ObjHeader* obj = reinterpret_cast<ObjHeader*>(header + 1);
for (int index = 0; index < header->objectCount_; index++) { for (int index = 0; index < header->objectCount(); index++) {
runDeallocationHooks(obj); runDeallocationHooks(obj);
obj = reinterpret_cast<ObjHeader*>( obj = reinterpret_cast<ObjHeader*>(
reinterpret_cast<uintptr_t>(obj) + objectSize(obj)); reinterpret_cast<uintptr_t>(obj) + objectSize(obj));
} }
memoryState->allocCount--; scheduleDestroyContainer(state, header);
konanFreeMemory(header);
} }
#endif #endif
@@ -444,7 +475,7 @@ void ObjectContainer::Init(const TypeInfo* type_info) {
header_ = AllocContainer(alloc_size); header_ = AllocContainer(alloc_size);
if (header_) { if (header_) {
// One object in this container. // One object in this container.
header_->objectCount_ = 1; header_->setObjectCount(1);
// header->refCount_ is zero initialized by AllocContainer(). // header->refCount_ is zero initialized by AllocContainer().
SetMeta(GetPlace(), type_info); SetMeta(GetPlace(), type_info);
#if TRACE_MEMORY #if TRACE_MEMORY
@@ -462,7 +493,7 @@ void ArrayContainer::Init(const TypeInfo* type_info, uint32_t elements) {
RuntimeAssert(header_ != nullptr, "Cannot alloc memory"); RuntimeAssert(header_ != nullptr, "Cannot alloc memory");
if (header_) { if (header_) {
// One object in this container. // One object in this container.
header_->objectCount_ = 1; header_->setObjectCount(1);
// header->refCount_ is zero initialized by AllocContainer(). // header->refCount_ is zero initialized by AllocContainer().
GetPlace()->count_ = elements; GetPlace()->count_ = elements;
SetMeta(GetPlace()->obj(), type_info); SetMeta(GetPlace()->obj(), type_info);
@@ -472,6 +503,8 @@ void ArrayContainer::Init(const TypeInfo* type_info, uint32_t elements) {
} }
} }
// TODO: store arena containers in some reuseable data structure, similar to
// finalizer queue.
void ArenaContainer::Init() { void ArenaContainer::Init() {
allocContainer(1024); allocContainer(1024);
} }
@@ -542,7 +575,7 @@ ObjHeader* ArenaContainer::PlaceObject(const TypeInfo* type_info) {
if (!result) { if (!result) {
return nullptr; return nullptr;
} }
currentChunk_->asHeader()->objectCount_++; currentChunk_->asHeader()->incObjectCount();
setMeta(result, type_info); setMeta(result, type_info);
return result; return result;
} }
@@ -554,7 +587,7 @@ ArrayHeader* ArenaContainer::PlaceArray(const TypeInfo* type_info, uint32_t coun
if (!result) { if (!result) {
return nullptr; return nullptr;
} }
currentChunk_->asHeader()->objectCount_++; currentChunk_->asHeader()->incObjectCount();
setMeta(result->obj(), type_info); setMeta(result->obj(), type_info);
result->count_ = count; result->count_ = count;
return result; return result;
@@ -610,6 +643,7 @@ MemoryState* InitMemory() {
memoryState->containers = konanConstructInstance<ContainerHeaderSet>(); memoryState->containers = konanConstructInstance<ContainerHeaderSet>();
#endif #endif
#if USE_GC #if USE_GC
memoryState->finalizerQueue = konanConstructInstance<ContainerHeaderDeque>();
memoryState->toFree = konanConstructInstance<ContainerHeaderSet>(); memoryState->toFree = konanConstructInstance<ContainerHeaderSet>();
memoryState->gcInProgress = false; memoryState->gcInProgress = false;
initThreshold(memoryState, kGcThreshold); initThreshold(memoryState, kGcThreshold);
@@ -641,11 +675,15 @@ void DeinitMemory(MemoryState* memoryState) {
} }
#endif #endif
konanDestructInstance(memoryState->finalizerQueue);
memoryState->finalizerQueue = nullptr;
#endif // USE_GC #endif // USE_GC
if (memoryState->allocCount > 0) { if (memoryState->allocCount > 0) {
fprintf(stderr, "*** Memory leaks, leaked %d containers ***\n",
memoryState->allocCount);
#if TRACE_MEMORY #if TRACE_MEMORY
fprintf(stderr, "*** Memory leaks, leaked %d containers ***\n", memoryState->allocCount);
dumpReachable("", memoryState->containers); dumpReachable("", memoryState->containers);
konanDestructInstance(memoryState->containers); konanDestructInstance(memoryState->containers);
memoryState->containers = nullptr; memoryState->containers = nullptr;
@@ -857,9 +895,8 @@ void GarbageCollect() {
// Traverse all elements, and collect those not having CONTAINER_TAG_SEEN and zero RC. // Traverse all elements, and collect those not having CONTAINER_TAG_SEEN and zero RC.
// Clear CONTAINER_TAG_SEEN while traversing on live elements, set in on dead elements. // Clear CONTAINER_TAG_SEEN while traversing on live elements, set in on dead elements.
ContainerHeaderSet toRemove;
for (auto container : *state->toFree) { for (auto container : *state->toFree) {
phase4(container, &toRemove); phase4(state, container);
} }
#if TRACE_GC_PHASES #if TRACE_GC_PHASES
dumpReachable("P4", state->toFree); dumpReachable("P4", state->toFree);
@@ -868,10 +905,7 @@ void GarbageCollect() {
// Clear cycle candidates list. // Clear cycle candidates list.
state->toFree->clear(); state->toFree->clear();
for (auto header : toRemove) { processFinalizerQueue(state);
RuntimeAssert((header->refCount_ & CONTAINER_TAG_SEEN) != 0, "Must be not seen");
FreeContainerNoRef(header);
}
state->gcInProgress = false; state->gcInProgress = false;
} }
+45 -4
View File
@@ -23,6 +23,8 @@
// Must fit in two bits. // Must fit in two bits.
typedef enum { typedef enum {
// Those bit masks are applied to refCount_ field.
// Container is normal thread local container. // Container is normal thread local container.
CONTAINER_TAG_NORMAL = 0, CONTAINER_TAG_NORMAL = 0,
// Container shall be atomically refcounted, currently disabled. // Container shall be atomically refcounted, currently disabled.
@@ -39,7 +41,19 @@ typedef enum {
// Actual value to increment/decrement container by. Tag is in lower bits. // Actual value to increment/decrement container by. Tag is in lower bits.
CONTAINER_TAG_INCREMENT = 1 << CONTAINER_TAG_SHIFT, CONTAINER_TAG_INCREMENT = 1 << CONTAINER_TAG_SHIFT,
// Mask for container type, disregard seen bit. // Mask for container type, disregard seen bit.
CONTAINER_TAG_MASK = ((CONTAINER_TAG_INCREMENT >> 1) - 1) CONTAINER_TAG_MASK = ((CONTAINER_TAG_INCREMENT >> 1) - 1),
// Those bit masks are applied to objectCount_ field.
// Shift to get actual object count.
CONTAINER_TAG_GC_SHIFT = 3,
CONTAINER_TAG_GC_INCREMENT = 1 << CONTAINER_TAG_GC_SHIFT,
// Color of a container.
CONTAINER_TAG_GC_COLOR_MASK = ((CONTAINER_TAG_GC_INCREMENT >> 1) - 1),
// Colors.
CONTAINER_TAG_GC_BLACK = 0,
CONTAINER_TAG_GC_GRAY = 1,
CONTAINER_TAG_GC_WHITE = 2,
CONTAINER_TAG_GC_PURPLE = 3
} ContainerTag; } ContainerTag;
typedef uint32_t container_offset_t; typedef uint32_t container_offset_t;
@@ -47,11 +61,38 @@ typedef uint32_t container_size_t;
// Header of all container objects. Contains reference counter. // Header of all container objects. Contains reference counter.
struct ContainerHeader { struct ContainerHeader {
// Reference counter of container. Uses two lower bits of counter for // Reference counter of container. Uses CONTAINER_TAG_SHIFT,lower bits of counter
// container type (for polymorphism in ::Release()). // for container type (for polymorphism in ::Release()).
volatile uint32_t refCount_; uint32_t refCount_;
// Number of objects in the container. // Number of objects in the container.
uint32_t objectCount_; uint32_t objectCount_;
inline unsigned refCount() const {
return refCount_ >> CONTAINER_TAG_SHIFT;
}
inline void incRefCount() {
refCount_ += CONTAINER_TAG_INCREMENT;
}
inline int decRefCount() {
refCount_ -= CONTAINER_TAG_INCREMENT;
return refCount_ >> CONTAINER_TAG_SHIFT;
}
inline unsigned objectCount() const {
return objectCount_ >> CONTAINER_TAG_GC_SHIFT;
}
inline void incObjectCount() {
objectCount_ += CONTAINER_TAG_GC_INCREMENT;
}
inline void setObjectCount(int count) {
objectCount_ = count << CONTAINER_TAG_GC_SHIFT;
}
inline unsigned color() const {
return objectCount_ & CONTAINER_TAG_GC_COLOR_MASK;
}
void setColor(unsigned color) {
objectCount_ = (objectCount_ & ~CONTAINER_TAG_GC_COLOR_MASK) | color;
}
}; };
struct ArrayHeader; struct ArrayHeader;