From 176b417c473a659202158e279d77368b09f9383d Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 11 Apr 2024 14:37:30 +0200 Subject: [PATCH] Start working on reducing the memory consumption --- .../src/internal/LockFreeLinkedList.common.kt | 26 ++++++++----------- .../jvm/test/MemoryFootprintTest.kt | 23 ++++++++++++++++ 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt index c5544f0aa9..9b4adbf3af 100644 --- a/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt +++ b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt @@ -46,20 +46,18 @@ internal open class LockFreeLinkedListNode { } /** @suppress **This is unstable API and it is subject to change.** */ -internal open class LockFreeLinkedListHead { - private val head = LockFreeLinkedListSegment( - id = 0, - prev = null, - pointers = 2, - head = this, - ) - private val tail = atomic(head) +internal open class LockFreeLinkedListHead: LockFreeLinkedListSegment( + id = 0, + prev = null, + pointers = 2, +) { + private val tail = atomic(this) private val nextElement = atomic(0L) /** * The list of bits that are forbidden from entering the list. * - * TODO: we can store this in the extra bits in [head], there's enough space for that there, and it's never removed. + * TODO: we can store this in `cleanedAndPointers`, there's enough space for that there. */ private val forbiddenBits: AtomicInt = atomic(0) @@ -122,17 +120,14 @@ internal open class LockFreeLinkedListHead { null } } + + override val head: LockFreeLinkedListHead get() = this } internal open class LockFreeLinkedListSegment( id: Long, prev: LockFreeLinkedListSegment?, pointers: Int, - /** Used only during promoting of a single node to a list to ensure wait-freedom of the promotion operation. - * Without this, promotion can't be implemented without a (possibly bounded) spin loop: once the node is committed - * to be part of some list, the other threads can't do anything until that one thread sets the state to be the - * head of the list. */ - @JvmField val head: LockFreeLinkedListHead, ) : Segment(id = id, prev = prev, pointers = pointers) { /** Each cell is a [LockFreeLinkedListNode], a [BrokenForSomeElements], or `null`. */ @@ -188,6 +183,8 @@ internal open class LockFreeLinkedListSegment( override fun onCancellation(index: Int, cause: Throwable?, context: CoroutineContext) { throw UnsupportedOperationException("Cancellation is not supported on LockFreeLinkedList") } + + open val head: LockFreeLinkedListHead get() = prev!!.head } internal class Address(@JvmField val segment: LockFreeLinkedListSegment, @JvmField val index: Int) @@ -197,7 +194,6 @@ private fun createSegment(id: Long, prev: LockFreeLinkedListSegment): LockFreeLi id = id, prev = prev, pointers = 0, - head = prev.head ) private const val SEGMENT_SIZE = 8 diff --git a/kotlinx-coroutines-core/jvm/test/MemoryFootprintTest.kt b/kotlinx-coroutines-core/jvm/test/MemoryFootprintTest.kt index 8f78a92634..a2ad7498fc 100644 --- a/kotlinx-coroutines-core/jvm/test/MemoryFootprintTest.kt +++ b/kotlinx-coroutines-core/jvm/test/MemoryFootprintTest.kt @@ -3,6 +3,7 @@ package kotlinx.coroutines import kotlinx.coroutines.testing.* import org.junit.Test import org.openjdk.jol.info.ClassLayout +import org.openjdk.jol.info.GraphLayout import kotlin.test.* @@ -14,9 +15,31 @@ class MemoryFootprintTest : TestBase(true) { @Test fun testCancellableContinuationFootprint() = assertLayout(CancellableContinuationImpl::class.java, 48) + + @Test + fun testJobSize() { + assertTotalSize(jobWithChildren(1), 112) + assertTotalSize(jobWithChildren(2), 336) // originally: 192 + assertTotalSize(jobWithChildren(3), 416) // originally: 56 + assertTotalSize(jobWithChildren(4), 496) // originally: 304 + } + + private fun jobWithChildren(numberOfChildren: Int): Job { + val result = Job() + repeat(numberOfChildren) { + Job(result) + } + return result + } + private fun assertLayout(clz: Class<*>, expectedSize: Int) { val size = ClassLayout.parseClass(clz).instanceSize() // println(ClassLayout.parseClass(clz).toPrintable()) assertEquals(expectedSize.toLong(), size) } + + private fun assertTotalSize(instance: Job, expectedSize: Int) { + val size = GraphLayout.parseInstance(instance).totalSize() + assertEquals(expectedSize.toLong(), size) + } }