Skip to content

Commit

Permalink
Start working on reducing the memory consumption
Browse files Browse the repository at this point in the history
  • Loading branch information
dkhalanskyjb committed Apr 11, 2024
1 parent 1770738 commit 176b417
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 15 deletions.
Expand Up @@ -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<LockFreeLinkedListSegment>(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)

Expand Down Expand Up @@ -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<LockFreeLinkedListSegment>(id = id, prev = prev, pointers = pointers)
{
/** Each cell is a [LockFreeLinkedListNode], a [BrokenForSomeElements], or `null`. */
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
23 changes: 23 additions & 0 deletions kotlinx-coroutines-core/jvm/test/MemoryFootprintTest.kt
Expand Up @@ -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.*


Expand All @@ -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)
}
}

0 comments on commit 176b417

Please sign in to comment.