-
Notifications
You must be signed in to change notification settings - Fork 564
Rework memory manager to make stack slot update faster #2813
Conversation
runtime/src/main/cpp/Memory.h
Outdated
@@ -455,6 +466,8 @@ extern "C" { | |||
ObjHeader* result = name(__VA_ARGS__, OBJ_RESULT); \ | |||
return result; \ | |||
} | |||
#define HEAP_RETURN_SLOT(slot) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not enforce return slot to be always stack-located instead?
I believe that this is
- possible
- simpler
- more efficient
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not just stack allocated, it must be in slots seen when walking the stack. Let's first stabilize this design and see its performance implications and the proceed with optimisations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did that help?
d28e986
to
309c62e
Compare
runtime/src/main/cpp/Memory.cpp
Outdated
// We do not use cycle collector for frozen objects, as we already detected | ||
// possible cycles during freezing. | ||
// Also do not use cycle collector for provable acyclic objects. | ||
int color = container->color(); | ||
if (color != CONTAINER_TAG_GC_PURPLE && color != CONTAINER_TAG_GC_GREEN) { | ||
RuntimeAssert(!container->shareable(), "Shareable cannot be cycle collected"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it the same as checked above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reworked.
inline void initThreshold(MemoryState* state, uint32_t gcThreshold) { | ||
state->gcThreshold = gcThreshold; | ||
state->toFree->reserve(gcThreshold); | ||
state->toRelease->reserve(gcThreshold); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was toFree->reserve(gcThreshold)
removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you suppose that it is unlikely to get filled up to threshold because toRelease
will become full earlier?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's no longer even close to that threshold.
header_ = AllocContainer(alloc_size); | ||
RuntimeCheck(header_ != nullptr, "Cannot alloc memory"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the check below isn't required anymore?
runtime/src/main/cpp/Memory.cpp
Outdated
|
||
#if GC_ERGONOMICS | ||
auto gcStartTime = konan::getTimeMicros(); | ||
#endif | ||
|
||
state->gcInProgress = true; | ||
|
||
incrementStack(state); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I understand correctly that long-living stack references become cycle candidates during every GC?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly. Any alternatives?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not yet, but I believe we should eventually apply some efforts to find some.
runtime/src/main/cpp/Memory.cpp
Outdated
@@ -1638,8 +1800,7 @@ void Kotlin_native_internal_GC_resume(KRef) { | |||
MemoryState* state = memoryState; | |||
if (state->gcSuspendCount > 0) { | |||
state->gcSuspendCount--; | |||
if (state->toFree != nullptr && | |||
freeableSize(state) >= state->gcThreshold) { | |||
if (state->toFree != nullptr && freeableSize(state) >= state->gcThreshold) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw, I guess gcSuspendCount == 0
should be checked here too.
runtime/src/main/cpp/Memory.cpp
Outdated
@@ -1650,6 +1811,7 @@ void Kotlin_native_internal_GC_stop(KRef) { | |||
#if USE_GC | |||
if (memoryState->toFree != nullptr) { | |||
GarbageCollect(); | |||
// TODO: what shall we do with toRelease here? Just leak? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw, this GC.start/stop feature doesn't seem to work properly.
Consider e.g.
kotlin.native.internal.GC.stop()
Any().freeze()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, more testing is needed, not sure if in this PR.
runtime/src/main/cpp/Worker.cpp
Outdated
} | ||
// Notify the future. | ||
job.future->storeResultUnlocked(result, ok); | ||
resultHolder.clear(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I see, by this moment another thread may have got this object already, so clearing the holder here may cause data race.
Btw, can transfer
have ObjHolder
parameter instead and clear it by itself? This looks making sense to me.
runtime/src/main/cpp/Memory.cpp
Outdated
if (stackReferences * 5 > state->gcThreshold) { | ||
auto newThreshold = state->gcThreshold * 3 / 2 + 1; | ||
if (newThreshold < kMaxErgonomicThreshold) { | ||
state->gcThreshold = newThreshold; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is space in toRelease
not reserved intentionally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reworked here.
runtime/src/main/cpp/Memory.cpp
Outdated
memoryState->toFree = nullptr; | ||
memoryState->roots = nullptr; | ||
state->gcSuspendCount = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if GC is stopped while suspended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hard to tell what is right, but moved zeroing above.
runtime/src/main/cpp/Memory.cpp
Outdated
state->toFree->push_back(container); | ||
if (state->gcSuspendCount == 0 && state->toFree->size() >= state->gcThreshold) { | ||
GC_LOG("Calling GC from DecrementRC: %d\n", state->toRelease->size()) | ||
garbageCollect(state, false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This DecrementRC
variant is used only during GC. What's the purpose of invoking GC inside it?
runtime/src/main/cpp/Memory.cpp
Outdated
@@ -111,9 +111,10 @@ volatile int allocCount = 0; | |||
volatile int aliveMemoryStatesCount = 0; | |||
|
|||
// Forward declarations. | |||
__attribute__((noinline)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NO_INLINE
?
runtime/src/main/cpp/Memory.cpp
Outdated
if (container->hasContainerSize() && container->containerSize() == size) { | ||
// TODO: shall it be == instead? | ||
if (container->hasContainerSize() && | ||
container->containerSize() >= size && container->containerSize() <= 2 * size) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
container->containerSize() <= 2 * size
is too permissive. Can it be more like container->containerSize() <= size + 8
?
runtime/src/main/cpp/Memory.cpp
Outdated
if (old != nullptr ) { | ||
ReleaseStackRef(old); | ||
} | ||
if (object != nullptr) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does removing old != object
check have significant positive effect?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just remove excessive branch, but can return.
21970da
to
d7dd6d2
Compare
// Class holding reference to an object, holding object during C++ scope. | ||
class ObjHolder { | ||
public: | ||
ObjHolder() : obj_(nullptr) {} | ||
ObjHolder() : obj_(nullptr) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it is actually now more like ObjStackHolder
, StackRefHolder
or even StackFrame
.
runtime/src/main/cpp/Worker.cpp
Outdated
@@ -62,10 +62,11 @@ enum { | |||
THREAD_LOCAL_VARIABLE KInt g_currentWorkerId = 0; | |||
|
|||
KNativePtr transfer(ObjHolder* holder, KInt mode) { | |||
holder->hold(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just
void* result = CreateStablePointer(holder.obj())
if (!ClearSubgraphReferences(holder->obj(), mode == CHECKED)) {
ThrowWorkerInvalidState();
}
holder.clear()
return result;
Technically the same, but avoids leaking abstractions and pairs with AdoptStablePointer
semantically.
No description provided.