From 3902f343b58c51547f7abe3655c48a72c7be4936 Mon Sep 17 00:00:00 2001 From: Jasjeet Singh <98077881+07jasjeet@users.noreply.github.com> Date: Wed, 13 Mar 2024 18:00:46 +0530 Subject: [PATCH 1/3] Update SuiteSourceGenerator.kt Now, if a parent class is abstract, that child class will also be benchmarked, with annotations derived in order: child ?: parent. --- .../benchmark/gradle/SuiteSourceGenerator.kt | 94 ++++++++++++++++--- 1 file changed, 82 insertions(+), 12 deletions(-) diff --git a/plugin/main/src/kotlinx/benchmark/gradle/SuiteSourceGenerator.kt b/plugin/main/src/kotlinx/benchmark/gradle/SuiteSourceGenerator.kt index 0563a469..44a825eb 100644 --- a/plugin/main/src/kotlinx/benchmark/gradle/SuiteSourceGenerator.kt +++ b/plugin/main/src/kotlinx/benchmark/gradle/SuiteSourceGenerator.kt @@ -3,6 +3,7 @@ package kotlinx.benchmark.gradle import com.squareup.kotlinpoet.* import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.name.* import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.annotations.* @@ -99,13 +100,56 @@ class SuiteSourceGenerator(val title: String, val module: ModuleDescriptor, val private fun processPackage(module: ModuleDescriptor, packageView: PackageViewDescriptor) { for (packageFragment in packageView.fragments.filter { it.module == module }) { - DescriptorUtils.getAllDescriptors(packageFragment.getMemberScope()) + val classDescriptors = DescriptorUtils.getAllDescriptors(packageFragment.getMemberScope()) .filterIsInstance() + + val abstractClassDescriptors = classDescriptors.filter { it.modality == Modality.ABSTRACT } + + // Abstract classes which are annotated directly. + val directlyAnnotatedAbstractClassDescriptors = abstractClassDescriptors .filter { it.annotations.any { it.fqName.toString() == stateAnnotationFQN } } - .filter { it.modality != Modality.ABSTRACT } - .forEach { - generateBenchmark(it) + + // Contains both directly and indirectly annotated ClassDescriptors. + val annotatedAbstractClassDescriptors = abstractClassDescriptors + .filter { abstractClass -> + directlyAnnotatedAbstractClassDescriptors.forEach { annotatedAbstractClass -> + if (abstractClass.isSubclassOf(annotatedAbstractClass)) { + // If any benchmark class is not annotated but extended with an abstract class + // annotated with @State, it should be included when generating benchmarks. + return@filter true + } + } + return@filter false + } + + fun ClassDescriptor.getParentAnnotated(): ClassDescriptor? { + annotatedAbstractClassDescriptors.forEach { annotatedAbstractClass -> + if (this.isSubclassOf(annotatedAbstractClass)) { + return annotatedAbstractClass + } } + return null + } + + val annotatedClassDescriptors = mutableListOf() + .apply { + classDescriptors + .filter { it.modality != Modality.ABSTRACT } + .forEach { + if (it.annotations.any { it.fqName.toString() == stateAnnotationFQN }) { + add(AnnotatedClassDescriptor(it)) + } else { + val parent = it.getParentAnnotated() + if (parent != null) { + add(AnnotatedClassDescriptor(it, parent)) + } + } + } + } + + annotatedClassDescriptors.forEach { + generateBenchmark(it) + } } for (subpackageName in module.getSubPackagesOf(packageView.fqName, MemberScope.ALL_NAME_FILTER)) { @@ -113,7 +157,34 @@ class SuiteSourceGenerator(val title: String, val module: ModuleDescriptor, val } } - private fun generateBenchmark(original: ClassDescriptor) { + /** @param parentAnnotatedClassDescriptor if null, [original] is directly annotated.*/ + private data class AnnotatedClassDescriptor( + val original: ClassDescriptor, + val parentAnnotatedClassDescriptor: ClassDescriptor? = null + ) + + private fun ClassDescriptor.getParameterProperties(): List = + DescriptorUtils.getAllDescriptors(this.unsubstitutedMemberScope) + .filterIsInstance() + .filter { it.annotations.any { it.fqName.toString() == paramAnnotationFQN } } + + private fun ClassDescriptor.getMeasureAnnotation(): AnnotationDescriptor? = + this.annotations.singleOrNull { it.fqName.toString() == measureAnnotationFQN } + + private fun ClassDescriptor.getWarmupAnnotation(): AnnotationDescriptor? = + this.annotations.singleOrNull { it.fqName.toString() == warmupAnnotationFQN } + + private fun ClassDescriptor.getOutputTimeAnnotation(): AnnotationDescriptor? = + this.annotations.singleOrNull { it.fqName.toString() == outputTimeAnnotationFQN } + + private fun ClassDescriptor.getModeAnnotation(): AnnotationDescriptor? = + this.annotations.singleOrNull { it.fqName.toString() == modeAnnotationFQN } + + + private fun generateBenchmark(classDescriptor: AnnotatedClassDescriptor) { + val original = classDescriptor.original + val parent = classDescriptor.parentAnnotatedClassDescriptor + val originalPackage = original.fqNameSafe.parent() val originalName = original.fqNameSafe.shortName() val originalClass = ClassName(originalPackage.toString(), originalName.toString()) @@ -125,14 +196,13 @@ class SuiteSourceGenerator(val title: String, val module: ModuleDescriptor, val val functions = DescriptorUtils.getAllDescriptors(original.unsubstitutedMemberScope) .filterIsInstance() - val parameterProperties = DescriptorUtils.getAllDescriptors(original.unsubstitutedMemberScope) - .filterIsInstance() - .filter { it.annotations.any { it.fqName.toString() == paramAnnotationFQN } } + val parameterProperties = original.getParameterProperties() - val measureAnnotation = original.annotations.singleOrNull { it.fqName.toString() == measureAnnotationFQN } - val warmupAnnotation = original.annotations.singleOrNull { it.fqName.toString() == warmupAnnotationFQN } - val outputTimeAnnotation = original.annotations.singleOrNull { it.fqName.toString() == outputTimeAnnotationFQN } - val modeAnnotation = original.annotations.singleOrNull { it.fqName.toString() == modeAnnotationFQN } + // original's annotations are given higher priority than parent's annotation. + val measureAnnotation = original.getMeasureAnnotation() ?: parent?.getMeasureAnnotation() + val warmupAnnotation = original.getWarmupAnnotation() ?: parent?.getWarmupAnnotation() + val outputTimeAnnotation = original.getOutputTimeAnnotation() ?: parent?.getOutputTimeAnnotation() + val modeAnnotation = original.getModeAnnotation() ?: parent?.getModeAnnotation() val outputTimeUnitValue = outputTimeAnnotation?.argumentValue("value") as EnumValue? val outputTimeUnit = outputTimeUnitValue?.enumEntryName?.toString() From e614a989e046b02a061fa251ac37c65b2ea2326d Mon Sep 17 00:00:00 2001 From: Jasjeet Singh <98077881+07jasjeet@users.noreply.github.com> Date: Wed, 13 Mar 2024 18:01:14 +0530 Subject: [PATCH 2/3] Created sample benchmark showing inheritability. --- .../benchmarks/src/TestInheritedBenchmark.kt | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 examples/kotlin/benchmarks/src/TestInheritedBenchmark.kt diff --git a/examples/kotlin/benchmarks/src/TestInheritedBenchmark.kt b/examples/kotlin/benchmarks/src/TestInheritedBenchmark.kt new file mode 100644 index 00000000..07fc499e --- /dev/null +++ b/examples/kotlin/benchmarks/src/TestInheritedBenchmark.kt @@ -0,0 +1,34 @@ +package test + +import org.openjdk.jmh.annotations.* +import java.util.concurrent.TimeUnit +import kotlin.math.cos +import kotlin.math.sqrt + +@State(Scope.Benchmark) +@Fork(1) +@Warmup(iterations = 0) +@Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) +abstract class BaseBenchmark { + + protected lateinit var data: TestData + + @Setup + fun setUp() { + data = TestData(50.0) + } +} + +class TestInheritedBenchmark: BaseBenchmark() { + @Benchmark + fun sqrtBenchmark(): Double { + return sqrt(data.value) + } + + @Benchmark + fun cosBenchmark(): Double { + return cos(data.value) + } +} + + From ece831bc943c96c09d4ff560307aefac24ea4fcc Mon Sep 17 00:00:00 2001 From: Jasjeet Singh <98077881+07jasjeet@users.noreply.github.com> Date: Wed, 13 Mar 2024 21:45:58 +0530 Subject: [PATCH 3/3] Update SuiteSourceGenerator.kt 1. Refactored code (cleanup) 2. All classes except final are considered when considering parent annotated class. --- .../benchmark/gradle/SuiteSourceGenerator.kt | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/plugin/main/src/kotlinx/benchmark/gradle/SuiteSourceGenerator.kt b/plugin/main/src/kotlinx/benchmark/gradle/SuiteSourceGenerator.kt index 44a825eb..664fd1b0 100644 --- a/plugin/main/src/kotlinx/benchmark/gradle/SuiteSourceGenerator.kt +++ b/plugin/main/src/kotlinx/benchmark/gradle/SuiteSourceGenerator.kt @@ -103,17 +103,17 @@ class SuiteSourceGenerator(val title: String, val module: ModuleDescriptor, val val classDescriptors = DescriptorUtils.getAllDescriptors(packageFragment.getMemberScope()) .filterIsInstance() - val abstractClassDescriptors = classDescriptors.filter { it.modality == Modality.ABSTRACT } + val inheritableClassDescriptors = classDescriptors.filter { it.modality != Modality.FINAL } // Abstract classes which are annotated directly. - val directlyAnnotatedAbstractClassDescriptors = abstractClassDescriptors + val directlyAnnotatedInheritableClassDescriptors = inheritableClassDescriptors .filter { it.annotations.any { it.fqName.toString() == stateAnnotationFQN } } // Contains both directly and indirectly annotated ClassDescriptors. - val annotatedAbstractClassDescriptors = abstractClassDescriptors - .filter { abstractClass -> - directlyAnnotatedAbstractClassDescriptors.forEach { annotatedAbstractClass -> - if (abstractClass.isSubclassOf(annotatedAbstractClass)) { + val annotatedInheritableClassDescriptors = inheritableClassDescriptors + .filter { inheritableClass -> + directlyAnnotatedInheritableClassDescriptors.forEach { annotatedInheritableClass -> + if (inheritableClass.isSubclassOf(annotatedInheritableClass)) { // If any benchmark class is not annotated but extended with an abstract class // annotated with @State, it should be included when generating benchmarks. return@filter true @@ -122,24 +122,17 @@ class SuiteSourceGenerator(val title: String, val module: ModuleDescriptor, val return@filter false } - fun ClassDescriptor.getParentAnnotated(): ClassDescriptor? { - annotatedAbstractClassDescriptors.forEach { annotatedAbstractClass -> - if (this.isSubclassOf(annotatedAbstractClass)) { - return annotatedAbstractClass - } - } - return null - } - val annotatedClassDescriptors = mutableListOf() .apply { classDescriptors .filter { it.modality != Modality.ABSTRACT } .forEach { - if (it.annotations.any { it.fqName.toString() == stateAnnotationFQN }) { + if (it.annotations.any { + annotationDescriptor -> annotationDescriptor.fqName.toString() == stateAnnotationFQN + }) { add(AnnotatedClassDescriptor(it)) } else { - val parent = it.getParentAnnotated() + val parent = it.getParentAnnotated(annotatedInheritableClassDescriptors) if (parent != null) { add(AnnotatedClassDescriptor(it, parent)) } @@ -157,7 +150,22 @@ class SuiteSourceGenerator(val title: String, val module: ModuleDescriptor, val } } - /** @param parentAnnotatedClassDescriptor if null, [original] is directly annotated.*/ + /** @param annotatedInheritableClassDescriptors descriptors of classes which are inheritable and directly annotated. + * with [stateAnnotationFQN]. + * @return Top most parent class descriptor which was annotated with [stateAnnotationFQN] and inherited by `this` class + * descriptor or null if none of its parent classes are not annotated.*/ + private fun ClassDescriptor.getParentAnnotated( + annotatedInheritableClassDescriptors: List + ): ClassDescriptor? { + annotatedInheritableClassDescriptors.forEach { annotatedAbstractClass -> + if (this.isSubclassOf(annotatedAbstractClass)) { + return annotatedAbstractClass + } + } + return null + } + + /** @param parentAnnotatedClassDescriptor if null, [original] is directly annotated with [stateAnnotationFQN].*/ private data class AnnotatedClassDescriptor( val original: ClassDescriptor, val parentAnnotatedClassDescriptor: ClassDescriptor? = null