-
Notifications
You must be signed in to change notification settings - Fork 564
Store initializing singletons in a separate thread local small map #4019
Changes from 4 commits
a5abaae
d28f122
39d678b
783e052
b52658e
7295cc0
b419101
4d63969
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/* | ||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license | ||
* that can be found in the LICENSE file. | ||
*/ | ||
|
||
package runtime.basic.initializers6 | ||
|
||
import kotlin.test.* | ||
|
||
import kotlin.native.concurrent.* | ||
|
||
val aWorkerId = 2 | ||
val bWorkersCount = 3 | ||
|
||
val aWorkerUnlocker = AtomicInt(0) | ||
val bWorkerUnlocker = AtomicInt(0) | ||
|
||
object A { | ||
init { | ||
// Must be called by aWorker only. | ||
assertEquals(aWorkerId, Worker.current.id) | ||
// Only allow b workers to run, when a worker has started initialization. | ||
bWorkerUnlocker.increment() | ||
// Only proceed with initialization, when all b workers have started executing. | ||
while (aWorkerUnlocker.value < bWorkersCount) {} | ||
// And now wait a bit, to increase probability of races. | ||
Worker.current.park(1000 * 1000L) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 100 ms would be enough :) |
||
} | ||
val a = produceA() | ||
val b = produceB() | ||
} | ||
|
||
fun produceA(): String { | ||
// Must've been called by aWorker only. | ||
assertEquals(aWorkerId, Worker.current.id) | ||
return "A" | ||
} | ||
|
||
fun produceB(): String { | ||
// Must've been called by aWorker only. | ||
assertEquals(aWorkerId, Worker.current.id) | ||
// Also check that it's ok to get A.a while initializing A.b. | ||
return "B+${A.a}" | ||
} | ||
|
||
@Test fun runTest() { | ||
val aWorker = Worker.start() | ||
// This test relies on aWorkerId value. | ||
assertEquals(aWorkerId, aWorker.id) | ||
val bWorkers = Array(bWorkersCount, { _ -> Worker.start() }) | ||
|
||
val aFuture = aWorker.execute(TransferMode.SAFE, {}, { | ||
A.b | ||
}) | ||
val bFutures = Array(bWorkers.size, { | ||
bWorkers[it].execute(TransferMode.SAFE, {}, { | ||
// Wait until A has started to initialize. | ||
while (bWorkerUnlocker.value < 1) {} | ||
// Now allow A initialization to continue. | ||
aWorkerUnlocker.increment() | ||
// And this should not've tried to init A itself. | ||
A.a + A.b | ||
}) | ||
}) | ||
|
||
for (future in bFutures) { | ||
assertEquals("AB+A", future.result) | ||
} | ||
assertEquals("B+A", aFuture.result) | ||
|
||
for (worker in bWorkers) { | ||
worker.requestTermination().result | ||
} | ||
aWorker.requestTermination().result | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/* | ||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license | ||
* that can be found in the LICENSE file. | ||
*/ | ||
|
||
package runtime.basic.initializers7 | ||
|
||
import kotlin.test.* | ||
|
||
import kotlin.random.Random | ||
|
||
object A { | ||
val a1 = Random.nextInt(100) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mean if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. |
||
val a2 = Random.nextInt(100) | ||
} | ||
|
||
object B { | ||
val b1 = A.a2 | ||
val b2 = C.c1 | ||
} | ||
|
||
object C { | ||
val c1 = Random.nextInt(100) | ||
val c2 = A.a1 | ||
val c3 = B.b1 | ||
val c4 = B.b2 | ||
} | ||
|
||
@Test fun runTest() { | ||
assertEquals(A.a1, C.c2) | ||
assertEquals(A.a2, C.c3) | ||
assertEquals(C.c1, C.c4) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -451,6 +451,10 @@ struct MemoryState { | |
uint64_t allocSinceLastGcThreshold; | ||
#endif // USE_GC | ||
|
||
// This map is expected to be small, std::map consumes less memory than | ||
// std::unordered_map and is just as efficient. | ||
KStdOrderedMap<ObjHeader**, ObjHeader*> initializingSingletons; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does using stack-like collection here have any effect on the performance? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does, actually. It outperforms |
||
|
||
#if COLLECT_STATISTIC | ||
#define CONTAINER_ALLOC_STAT(state, size, container) state->statistic.incAlloc(size, container); | ||
#define CONTAINER_DESTROY_STAT(state, container) \ | ||
|
@@ -1999,7 +2003,7 @@ OBJ_GETTER(initInstance, | |
|
||
template <bool Strict> | ||
OBJ_GETTER(initSharedInstance, | ||
ObjHeader** location, ObjHeader** localLocation, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) { | ||
ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) { | ||
#if KONAN_NO_THREADS | ||
ObjHeader* value = *location; | ||
if (value != nullptr) { | ||
|
@@ -2025,25 +2029,32 @@ OBJ_GETTER(initSharedInstance, | |
} | ||
#endif // KONAN_NO_EXCEPTIONS | ||
#else // KONAN_NO_THREADS | ||
ObjHeader* value = *localLocation; | ||
if (value != nullptr) RETURN_OBJ(value); | ||
auto it = memoryState->initializingSingletons.find(location); | ||
if (it != memoryState->initializingSingletons.end()) { | ||
RETURN_OBJ(it->second); | ||
} | ||
|
||
ObjHeader* initializing = reinterpret_cast<ObjHeader*>(1); | ||
|
||
// Spin lock. | ||
ObjHeader* value = nullptr; | ||
while ((value = __sync_val_compare_and_swap(location, nullptr, initializing)) == initializing); | ||
if (value != nullptr) { | ||
// OK'ish, inited by someone else. | ||
RETURN_OBJ(value); | ||
} | ||
ObjHeader* object = AllocInstance(typeInfo, OBJ_RESULT); | ||
UpdateHeapRef(localLocation, object); | ||
auto insertIt = memoryState->initializingSingletons.insert({location, object}); | ||
RuntimeCheck(insertIt.second, "object cannot be assigned twice into initializingSingletons"); | ||
addHeapRef(object); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this actually required? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure, but what's protecting this reference (and all that it references) from being garbage collected while (potentially long) initialization is taking place? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The allocation above actually creates a stack reference. It is alive at least until the return from the function. |
||
#if KONAN_NO_EXCEPTIONS | ||
ctor(object); | ||
if (Strict) | ||
FreezeSubgraph(object); | ||
UpdateHeapRef(location, object); | ||
synchronize(); | ||
memoryState->initializingSingletons.erase(location); | ||
releaseHeapRef<Strict>(object); | ||
return object; | ||
#else // KONAN_NO_EXCEPTIONS | ||
try { | ||
|
@@ -2052,11 +2063,14 @@ OBJ_GETTER(initSharedInstance, | |
FreezeSubgraph(object); | ||
UpdateHeapRef(location, object); | ||
synchronize(); | ||
memoryState->initializingSingletons.erase(location); | ||
releaseHeapRef<Strict>(object); | ||
return object; | ||
} catch (...) { | ||
UpdateReturnRef(OBJ_RESULT, nullptr); | ||
zeroHeapRef(location); | ||
zeroHeapRef(localLocation); | ||
memoryState->initializingSingletons.erase(location); | ||
releaseHeapRef<Strict>(object); | ||
synchronize(); | ||
throw; | ||
} | ||
|
@@ -2803,12 +2817,12 @@ OBJ_GETTER(InitInstanceRelaxed, | |
} | ||
|
||
OBJ_GETTER(InitSharedInstanceStrict, | ||
ObjHeader** location, ObjHeader** localLocation, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) { | ||
RETURN_RESULT_OF(initSharedInstance<true>, location, localLocation, typeInfo, ctor); | ||
ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) { | ||
RETURN_RESULT_OF(initSharedInstance<true>, location, typeInfo, ctor); | ||
} | ||
OBJ_GETTER(InitSharedInstanceRelaxed, | ||
ObjHeader** location, ObjHeader** localLocation, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) { | ||
RETURN_RESULT_OF(initSharedInstance<false>, location, localLocation, typeInfo, ctor); | ||
ObjHeader** location, const TypeInfo* typeInfo, void (*ctor)(ObjHeader*)) { | ||
RETURN_RESULT_OF(initSharedInstance<false>, location, typeInfo, ctor); | ||
} | ||
|
||
void SetStackRefStrict(ObjHeader** location, const ObjHeader* object) { | ||
|
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 sure if this is always correct.