Skip to content
This repository has been archived by the owner on Aug 10, 2021. It is now read-only.

Rework memory manager to make stack slot update faster #2813

Merged
merged 23 commits into from
Apr 9, 2019
Merged

Conversation

olonho
Copy link
Contributor

@olonho olonho commented Mar 25, 2019

No description provided.

@@ -455,6 +466,8 @@ extern "C" {
ObjHeader* result = name(__VA_ARGS__, OBJ_RESULT); \
return result; \
}
#define HEAP_RETURN_SLOT(slot) \
Copy link
Collaborator

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

  1. possible
  2. simpler
  3. more efficient

Copy link
Contributor Author

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did that.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did that help?

@olonho olonho changed the title Cheaper stack Rework memory manager to make stack slot update faster Mar 27, 2019
@olonho olonho force-pushed the cheaper_stack branch 3 times, most recently from d28e986 to 309c62e Compare April 1, 2019 11:20
// 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");
Copy link
Collaborator

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?

Copy link
Contributor Author

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);
Copy link
Collaborator

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?

Copy link
Collaborator

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?

Copy link
Contributor Author

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");
Copy link
Collaborator

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?


#if GC_ERGONOMICS
auto gcStartTime = konan::getTimeMicros();
#endif

state->gcInProgress = true;

incrementStack(state);
Copy link
Collaborator

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. Any alternatives?

Copy link
Collaborator

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.

@@ -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) {
Copy link
Collaborator

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.

@@ -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?
Copy link
Collaborator

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()

Copy link
Contributor Author

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.

}
// Notify the future.
job.future->storeResultUnlocked(result, ok);
resultHolder.clear();
Copy link
Collaborator

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.

if (stackReferences * 5 > state->gcThreshold) {
auto newThreshold = state->gcThreshold * 3 / 2 + 1;
if (newThreshold < kMaxErgonomicThreshold) {
state->gcThreshold = newThreshold;
Copy link
Collaborator

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked here.

memoryState->toFree = nullptr;
memoryState->roots = nullptr;
state->gcSuspendCount = 0;
Copy link
Collaborator

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?

Copy link
Contributor Author

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.

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);
Copy link
Collaborator

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?

@@ -111,9 +111,10 @@ volatile int allocCount = 0;
volatile int aliveMemoryStatesCount = 0;

// Forward declarations.
__attribute__((noinline))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NO_INLINE?

if (container->hasContainerSize() && container->containerSize() == size) {
// TODO: shall it be == instead?
if (container->hasContainerSize() &&
container->containerSize() >= size && container->containerSize() <= 2 * size) {
Copy link
Collaborator

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?

if (old != nullptr ) {
ReleaseStackRef(old);
}
if (object != nullptr) {
Copy link
Collaborator

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?

Copy link
Contributor Author

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.

@olonho olonho force-pushed the cheaper_stack branch 2 times, most recently from 21970da to d7dd6d2 Compare April 9, 2019 07:09
// Class holding reference to an object, holding object during C++ scope.
class ObjHolder {
public:
ObjHolder() : obj_(nullptr) {}
ObjHolder() : obj_(nullptr) {
Copy link
Collaborator

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.

@@ -62,10 +62,11 @@ enum {
THREAD_LOCAL_VARIABLE KInt g_currentWorkerId = 0;

KNativePtr transfer(ObjHolder* holder, KInt mode) {
holder->hold();
Copy link
Collaborator

@SvyatoslavScherbina SvyatoslavScherbina Apr 9, 2019

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.

@olonho olonho merged commit d072920 into master Apr 9, 2019
@olonho olonho deleted the cheaper_stack branch April 9, 2019 13:40
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants