diff --git a/build.gradle.kts b/build.gradle.kts index 8e197a6..6a9a226 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,9 @@ plugins { id("root.publication") - alias(libs.plugins.multiplatform).apply(false) + alias(libs.plugins.kotlin.multiplatform).apply(false) + alias(libs.plugins.kotlin.allopen).apply(false) alias(libs.plugins.kotlinx.kover).apply(false) + alias(libs.plugins.kotlinx.benchmark).apply(false) } allprojects { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e18c050..1719a66 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,12 +1,17 @@ [versions] kotlin = "2.0.0" kotlinx-kover = "0.8.0" +kotlinx-benchmark = "0.4.10" nexus-publish = "2.0.0" [libraries] nexus-publish = { module = "io.github.gradle-nexus.publish-plugin:io.github.gradle-nexus.publish-plugin.gradle.plugin", version.ref = "nexus-publish" } +kotlinx-benchmark-runtime = { module = "org.jetbrains.kotlinx:kotlinx-benchmark-runtime", version.ref = "kotlinx-benchmark" } + [plugins] -multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +kotlin-allopen = { id = "org.jetbrains.kotlin.plugin.allopen", version.ref = "kotlin" } kotlinx-kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kotlinx-kover" } +kotlinx-benchmark = { id = "org.jetbrains.kotlinx.benchmark", version.ref = "kotlinx-benchmark" } diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index 7901dbd..ad666e0 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -37,6 +37,14 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +benchmark@*: + version "2.1.4" + resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629" + integrity sha512-l9MlfN4M1K/H2fbhfMy3B7vJd6AGKJVQn2h6Sg/Yx+KckoUA7ewS5Vv6TjSq18ooE1kS9hhAlQRH3AkXIh/aOQ== + dependencies: + lodash "^4.17.4" + platform "^1.3.3" + binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" @@ -281,6 +289,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash@^4.17.4: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + log-symbols@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" @@ -375,6 +388,11 @@ picomatch@^2.0.4, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +platform@^1.3.3: + version "1.3.6" + resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7" + integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg== + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -406,7 +424,7 @@ serialize-javascript@6.0.0: dependencies: randombytes "^2.1.0" -source-map-support@0.5.21: +source-map-support@*, source-map-support@0.5.21: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== diff --git a/ksoup-benchmark/build.gradle.kts b/ksoup-benchmark/build.gradle.kts new file mode 100644 index 0000000..5d32ea4 --- /dev/null +++ b/ksoup-benchmark/build.gradle.kts @@ -0,0 +1,144 @@ +import kotlinx.benchmark.gradle.JsBenchmarkTarget +import kotlinx.benchmark.gradle.JsBenchmarksExecutor +import kotlinx.benchmark.gradle.JvmBenchmarkTarget + +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.allopen) + alias(libs.plugins.kotlinx.benchmark) +} + +allOpen { + annotation("org.openjdk.jmh.annotations.State") +} + +kotlin { + applyDefaultHierarchyTemplate() + + jvm { + val mainCompilation = compilations["main"] + compilations.create("benchmark") { associateWith(mainCompilation) } + } + js(IR) { + nodejs() + val mainCompilation = compilations["main"] + compilations.create("defaultExecutor") { associateWith(mainCompilation) } + compilations.create("builtInExecutor") { associateWith(mainCompilation) } + } + @OptIn(org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl::class) + wasmJs().nodejs() + + // Native targets + iosX64() + iosArm64() + iosSimulatorArm64() + macosX64() + macosArm64() + linuxX64() + linuxArm64() + mingwX64() + + sourceSets.commonMain.dependencies { + implementation(projects.ksoupEntities) + implementation(projects.ksoupHtml) + + implementation(libs.kotlinx.benchmark.runtime) + } + + sourceSets.jsMain { + sourceSets["jsDefaultExecutor"].dependsOn(this) + sourceSets["jsBuiltInExecutor"].dependsOn(this) + } +} + +// Configure benchmark +benchmark { + configurations { + named("main") { // --> jvmBenchmark, jsBenchmark, Benchmark, benchmark + iterations = 5 // number of iterations + iterationTime = 300 + iterationTimeUnit = "ms" + advanced("jvmForks", 3) + advanced("jsUseBridge", true) + } + + register("params") { + iterations = 5 // number of iterations + iterationTime = 300 + iterationTimeUnit = "ms" + include("ParamBenchmark") + param("data", 5, 1, 8) + param("unused", 6, 9) + } + + register("fast") { // --> jvmFastBenchmark, jsFastBenchmark, FastBenchmark, fastBenchmark + include("Common") + exclude("long") + iterations = 5 + iterationTime = 300 // time in ms per iteration + iterationTimeUnit = "ms" // time in ms per iteration + advanced("nativeGCAfterIteration", true) + } + + register("csv") { + include("Common") + exclude("long") + iterations = 1 + iterationTime = 300 + iterationTimeUnit = "ms" + reportFormat = "csv" // csv report format + } + + register("fork") { + include("CommonBenchmark") + iterations = 5 + iterationTime = 300 + iterationTimeUnit = "ms" + advanced("jvmForks", "definedByJmh") // see README.md for possible "jvmForks" values + advanced("nativeFork", "perIteration") // see README.md for possible "nativeFork" values + } + } + + // Setup configurations + targets { + // This one matches target name, e.g. 'jvm', 'js', + // and registers its 'main' compilation, so 'jvm' registers 'jvmMain' + register("jvm") { + this as JvmBenchmarkTarget + jmhVersion = "1.21" + } + // This one matches source set name, e.g. 'jvmMain', 'jvmTest', etc + // and register the corresponding compilation (here the 'benchmark' compilation declared in the 'jvm' target) + register("jvmBenchmark") { + this as JvmBenchmarkTarget + jmhVersion = "1.21" + } + register("jsDefaultExecutor") + register("jsBuiltInExecutor") { + this as JsBenchmarkTarget + jsBenchmarksExecutor = JsBenchmarksExecutor.BuiltIn + } + register("wasmJs") + + // Native targets + register("iosX64") + register("iosArm64") + register("iosSimulatorArm64") + register("macosX64") + register("macosArm64") + register("linuxX64") + register("linuxArm64") + register("mingwX64") + } +} + +// Node.js with canary v8 that supports recent Wasm GC changes +rootProject.extensions.findByType()?.apply { + version = "21.0.0-v8-canary202309167e82ab1fa2" + downloadBaseUrl = "https://nodejs.org/download/v8-canary" +} + +// Drop this when node js version become stable +configure(rootProject.tasks.withType(org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask::class)) { + args.add("--ignore-engines") +} diff --git a/ksoup-benchmark/src/commonMain/kotlin/benchmark/CommonBenchmark.kt b/ksoup-benchmark/src/commonMain/kotlin/benchmark/CommonBenchmark.kt new file mode 100644 index 0000000..d916d79 --- /dev/null +++ b/ksoup-benchmark/src/commonMain/kotlin/benchmark/CommonBenchmark.kt @@ -0,0 +1,37 @@ +package benchmark + +import com.mohamedrejeb.ksoup.html.parser.KsoupHtmlHandler +import com.mohamedrejeb.ksoup.html.parser.KsoupHtmlParser +import kotlinx.benchmark.* + +@State(Scope.Benchmark) +@Measurement(iterations = 3, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) +@OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) +@BenchmarkMode(Mode.AverageTime) +class CommonBenchmark { + private lateinit var handler: KsoupHtmlHandler + private lateinit var parser: KsoupHtmlParser + + @Setup + fun setUp() { + handler = KsoupHtmlHandler + .Builder() + .build() + parser = KsoupHtmlParser(handler = handler) + } + + @TearDown + fun teardown() { + parser.end() + } + + @Benchmark + fun parseHtml() { + parser.write("Test

Test

") + } + + @Benchmark + fun parseComplexAndLongHtml() { + parser.write("Test

Test

".repeat(100)) + } +} \ No newline at end of file diff --git a/ksoup-entities/build.gradle.kts b/ksoup-entities/build.gradle.kts index ccf24dd..8433df5 100644 --- a/ksoup-entities/build.gradle.kts +++ b/ksoup-entities/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - alias(libs.plugins.multiplatform) + alias(libs.plugins.kotlin.multiplatform) id("module.publication") } @@ -35,19 +35,13 @@ kotlin { @OptIn(org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl::class) wasmWasi().nodejs() - sourceSets { - /* Main source sets */ - val commonMain by getting { - dependencies { - // The library is lightweight, we don't use any other dependencies :D - } - } + /* Main source sets */ + sourceSets.commonMain.dependencies { + // The library is lightweight, we don't use any other dependencies :D + } - /* Test source sets */ - val commonTest by getting { - dependencies { - implementation(kotlin("test")) - } - } + /* Test source sets */ + sourceSets.commonTest.dependencies { + implementation(kotlin("test")) } } diff --git a/ksoup-html/build.gradle.kts b/ksoup-html/build.gradle.kts index 6892865..87ce229 100644 --- a/ksoup-html/build.gradle.kts +++ b/ksoup-html/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - alias(libs.plugins.multiplatform) + alias(libs.plugins.kotlin.multiplatform) id("module.publication") } @@ -35,20 +35,14 @@ kotlin { @OptIn(org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl::class) wasmWasi().nodejs() - sourceSets { - /* Main source sets */ - val commonMain by getting { - dependencies { - implementation(project(":ksoup-entities")) - // The library is lightweight, we don't use any other dependencies :D - } - } + /* Main source sets */ + sourceSets.commonMain.dependencies { + implementation(projects.ksoupEntities) + // The library is lightweight, we don't use any other dependencies :D + } - /* Test source sets */ - val commonTest by getting { - dependencies { - implementation(kotlin("test")) - } - } + /* Test source sets */ + sourceSets.commonTest.dependencies { + implementation(kotlin("test")) } } diff --git a/settings.gradle.kts b/settings.gradle.kts index c8c026c..51ee413 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,7 @@ rootProject.name = "Ksoup" +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + pluginManagement { includeBuild("convention-plugins") repositories { @@ -24,4 +26,7 @@ plugins { include( ":ksoup-html", ":ksoup-entities", + + // Benchmark module + ":ksoup-benchmark", ) \ No newline at end of file