Skip to content

Conversation

@alfonsocv12
Copy link
Contributor

After looking at the profile I found some sections that can be process in parallel to improve framework generation speeds

Screenshot 2025-11-13 at 10 59 57 a m

KT-82436

}

dependencies {
implementation(libs.kotlinx.coroutines.core)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, no other part of the compiler uses kotlinx.coroutines. If this is the case, this change effectively adds a new dependency.
Can the parallelization be done using standard JDK means?

For example, another part of the K/N compiler uses java.util.concurrent.Executors:

val executor = Executors.newFixedThreadPool(threadsCount)
val thrownFromThread = AtomicReference<Throwable?>(null)
val tasks = fragmentsList.zip(generationStates).map { (fragment, generationState) ->
Callable {
try {
// Currently, it's not possible to initialize the correct thread on `PerformanceManager` creation
// because new threads are spawned here when `fragment` with its `PerformanceManager` is already initialized.
fragment.performanceManager?.initializeCurrentThread()
runAfterLowerings(fragment, generationState)
} catch (t: Throwable) {
thrownFromThread.set(t)
}
}
}
executor.invokeAll(tasks.toList())
executor.shutdown()
executor.awaitTermination(1, TimeUnit.DAYS)
thrownFromThread.get()?.let { throw it }

Maybe it is worth checking other parts of the Kotlin compiler to see what is common.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think theres more places that are using them already. This search returns 17 build files with the dependency

Comment on lines 121 to 149
val classesToTranslate = mutableListOf<ClassDescriptor>()

runBlocking {
packageFragments.forEach { packageFragment ->
val memberScope = packageFragment.getMemberScope()

launch(Dispatchers.Default) {
memberScope.getContributedDescriptors()
.asSequence()
.filterIsInstance<CallableMemberDescriptor>()
.filter { mapper.shouldBeExposed(it) }
.forEach {
val classDescriptor = getClassIfCategory(it)
if (classDescriptor == null) {
topLevel.getOrPut(it.findSourceFile(), { mutableListOf() }) += it
} else {
// If a class is hidden from Objective-C API then it is meaningless
// to export its extensions.
if (!classDescriptor.isHiddenFromObjC()) {
extensions.getOrPut(classDescriptor, { mutableListOf() }) += it
}
}
}
}
}
}

val classesToTranslate = mutableListOf<ClassDescriptor>()

packageFragments.forEach { packageFragment ->
packageFragment.getMemberScope().collectClasses(classesToTranslate)
launch(Dispatchers.Default) {
memberScope.collectClasses(classesToTranslate)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ObjCExport header generation process is very order-specific. For example, depending on the translation order, the same declarations may get different names when there are clashes that need mangling.

IIUC, this PR changes the translation order (and therefore is a breaking change) and makes it non-deterministic (which is highly undesirable, as the compiler should produce the same output for the same inputs).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @SvyatoslavScherbina I think this should solve the order problem WDYT?

@alfonsocv12 alfonsocv12 force-pushed the feat/improve-objc-header-generator/multithread-package-framents branch from 1cf97df to e5d5e4d Compare November 20, 2025 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants