forked from Kotlin/kotlinx.coroutines
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Optimization: resizable workers array (Kotlin#3137)
Instead of allocating an array of maxPoolSize (~2M) elements for the worst-case supported scenario that may never be reached in practice and takes considerable memory, allocate just an array of corePoolSize elements and grow it dynamically if needed to accommodate more workers. The data structure to make it happen must support lock-free reads for performance reasons, but it is simple since the workers array is modified exclusively under synchronization.
- Loading branch information
1 parent
15e969e
commit 704841b
Showing
3 changed files
with
70 additions
and
6 deletions.
There are no files selected for viewing
38 changes: 38 additions & 0 deletions
38
kotlinx-coroutines-core/jvm/src/internal/ResizableAtomicArray.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* | ||
* Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. | ||
*/ | ||
|
||
package kotlinx.coroutines.internal | ||
|
||
import java.util.concurrent.atomic.* | ||
|
||
/** | ||
* Atomic array with lock-free reads and synchronized modifications. It logically has an unbounded size, | ||
* is implicitly filled with nulls, and is resized on updates as needed to grow. | ||
*/ | ||
internal class ResizableAtomicArray<T>(initialLength: Int) { | ||
@Volatile | ||
private var array = AtomicReferenceArray<T>(initialLength) | ||
|
||
// for debug output | ||
public fun currentLength(): Int = array.length() | ||
|
||
public operator fun get(index: Int): T? { | ||
val array = this.array // volatile read | ||
return if (index < array.length()) array[index] else null | ||
} | ||
|
||
// Must not be called concurrently, e.g. always use synchronized(this) to call this function | ||
fun setSynchronized(index: Int, value: T?) { | ||
val curArray = this.array | ||
val curLen = curArray.length() | ||
if (index < curLen) { | ||
curArray[index] = value | ||
} else { | ||
val newArray = AtomicReferenceArray<T>((index + 1).coerceAtLeast(2 * curLen)) | ||
for (i in 0 until curLen) newArray[i] = curArray[i] | ||
newArray[index] = value | ||
array = newArray // copy done | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
kotlinx-coroutines-core/jvm/test/lincheck/ResizableAtomicArrayLincheckTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. | ||
*/ | ||
|
||
package kotlinx.coroutines.lincheck | ||
|
||
import kotlinx.coroutines.* | ||
import kotlinx.coroutines.internal.* | ||
import org.jetbrains.kotlinx.lincheck.annotations.* | ||
import org.jetbrains.kotlinx.lincheck.paramgen.* | ||
|
||
@Param(name = "index", gen = IntGen::class, conf = "0:4") | ||
@Param(name = "value", gen = IntGen::class, conf = "1:5") | ||
@OpGroupConfig(name = "sync", nonParallel = true) | ||
class ResizableAtomicArrayLincheckTest : AbstractLincheckTest() { | ||
private val a = ResizableAtomicArray<Int>(2) | ||
|
||
@Operation | ||
fun get(@Param(name = "index") index: Int): Int? = a[index] | ||
|
||
@Operation(group = "sync") | ||
fun set(@Param(name = "index") index: Int, @Param(name = "value") value: Int) { | ||
a.setSynchronized(index, value) | ||
} | ||
|
||
override fun extractState() = (0..4).map { a[it] } | ||
} |