Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Noticeable upfront cost of Dispatchers.Default #4051

Closed
semoro opened this issue Feb 23, 2024 · 1 comment
Closed

Noticeable upfront cost of Dispatchers.Default #4051

semoro opened this issue Feb 23, 2024 · 1 comment

Comments

@semoro
Copy link
Contributor

semoro commented Feb 23, 2024

Describe the problem

Creation of the CoroutineScheduler requires initialization of kotlin.random.Random, which, in the case of kotlin-stdlib-jdk8 is used, requires dynamic class-loading of android.os.BUNDLE (see: https://youtrack.jetbrains.com/issue/KT-44089)

However, Dispatchers.Default is often used early in the application runtime when startup logic uses coroutines, and additional class-loading delays are unfortunate.
See github: "main" /Dispatchers.Default/ for examples.

Profiler result Screenshot 2024-02-23 at 23 26 13

Provide a Reproducer

// FILE: main.kt
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking


fun main() {
    val dispatcher = Dispatchers.Default
    runBlocking {
        delay(1000)
    }

    subCall(dispatcher)
}

suspend fun mainImpl() {
    println("done")
}
// FILE: other.kt
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.runBlocking

fun subCall(dispatcher: CoroutineDispatcher) {
    // This runBlocking is causing a long kotlin.Random.<clinit>
    runBlocking(dispatcher) {
        mainImpl()
    }
}
build.gradle.kts
// FILE: build.gradle.kts

plugins {
  kotlin("jvm") version "1.9.21"
}

group = "org.example"
version = "1.0-SNAPSHOT"

repositories {
  mavenCentral()
}

dependencies {
  implementation(kotlin("stdlib-jdk8"))
  testImplementation("org.jetbrains.kotlin:kotlin-test")

  // dependency on Kotlinx-coroutines
  implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
}
kotlin {
  jvmToolchain(21)
}
semoro added a commit that referenced this issue Feb 23, 2024
…Worker` init

During the creation of `CoroutineScheduler.Worker`, we need to initialize its embedded random number generator.
To avoid additional class-loading costs (#4051), `rngState` is now directly initialized from the current nanoTime (that is used as seed).

Also, a potential bug is fixed, where `nextInt` will always return zero due to incorrect initialization of rngState with zero.
@qwwdfsad
Copy link
Member

qwwdfsad commented Feb 24, 2024

Worth noting that this code path is noticeable during our IDE startup, when Dispatchers.Default is initialized

Additional validation:

  1. Running the trivial runBlocking + launch(Dispatchers.Default) with async-profiler start-n-profile.
    Note that it requires really high sampling interval
Flamegraph image

Can be run as java -agentpath:/Users/qwwdfsad/workspace/async-profiler-3.0-macos/lib/libasyncProfiler.dylib=start,event=cpu,interval=100000,file=profile.html -jar main.jar.
In short, ~10% of the main code spent in the initialization of the first worker, ~70% of that time is spent in Random initialization

  1. Startup is a subtle matter, so it's worth double-checking with something more deterministic.
    I've used -XX:+TraceBytecodes (note: you'll need fastdebug JDK for that).

The resulting flamegraph:

Flamegraph ![bytecodes.svg](https://github.com/Kotlin/kotlinx.coroutines/assets/2780116/7117041e-a2e8-426c-80a4-57ea5e819478)

The same results w.r.t. relative cost

knisht pushed a commit to JetBrains/intellij-deps-kotlinx.coroutines that referenced this issue Apr 15, 2024
…id state (Kotlin#4052)

A potential bug is fixed, where `nextInt` would always return zero due to incorrect initialization of rngState with zero.
This could happen during the creation of a worker with thread id = `1595972770` (JDK >=8), or unpredictable if fallback thread-local random is used (android SDK <34 or JDK <7), approximate probability is 2.4E-10

Also, this slightly optimizes the performance of coroutines initialization. During the creation of `CoroutineScheduler.Worker`, we need to initialize its embedded random number generator.
To avoid additional class-loading costs (Kotlin#4051), `rngState` is now directly initialized from the current nanoTime (that is used as seed).

Fixes Kotlin#4051

Co-authored-by: Vsevolod Tolstopyatov <qwwdfsad@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants