Skip to content

Commit

Permalink
Add basic microbenchmarks for algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
alexvanyo committed Jan 4, 2025
1 parent d3ebfc8 commit 925d340
Show file tree
Hide file tree
Showing 14 changed files with 392 additions and 0 deletions.
3 changes: 3 additions & 0 deletions algorithm-benchmark/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# algorithm-benchmark

A benchmark module for [algorithm](../algorithm)
75 changes: 75 additions & 0 deletions algorithm-benchmark/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import com.alexvanyo.composelife.buildlogic.FormFactor
import com.alexvanyo.composelife.buildlogic.configureGradleManagedDevices
import com.slack.keeper.KeeperExtension
import com.slack.keeper.optInToKeeper

plugins {
alias(libs.plugins.convention.kotlinMultiplatform)
alias(libs.plugins.convention.androidApplication)
alias(libs.plugins.convention.androidApplicationCompose)
alias(libs.plugins.convention.androidApplicationTesting)
alias(libs.plugins.convention.detekt)
alias(libs.plugins.convention.kotlinMultiplatformCompose)
kotlin("plugin.serialization") version libs.versions.kotlin
alias(libs.plugins.gradleDependenciesSorter)
alias(libs.plugins.keeper)
}

android {
namespace = "com.alexvanyo.composelife.algorithm.benchmark"
defaultConfig {
applicationId = "com.alexvanyo.composelife.algorithm.benchmark"
minSdk = 21
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.benchmark.junit4.AndroidBenchmarkRunner"
testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "EMULATOR"
}
configureGradleManagedDevices(enumValues<FormFactor>().toSet(), this)
}

keeper {
automaticR8RepoManagement.set(false)
traceReferences {}
}

kotlin {
androidTarget()

sourceSets {
val androidMain by getting {
dependencies {
implementation(libs.androidx.benchmark.micro.junit4)
}
}
val commonTest by getting {
dependencies {
implementation(projects.algorithm)
implementation(projects.dispatchersTest)
implementation(projects.patterns)
}
}
val androidInstrumentedTest by getting {
dependencies {
implementation(libs.testParameterInjector.junit4)
}
}
}
}
21 changes: 21 additions & 0 deletions algorithm-benchmark/consumer-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
21 changes: 21 additions & 0 deletions algorithm-benchmark/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
15 changes: 15 additions & 0 deletions algorithm-benchmark/release-badging.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package: name='com.alexvanyo.composelife.algorithm.benchmark' versionCode='1' versionName='1.0' platformBuildVersionName='15' platformBuildVersionCode='35' compileSdkVersion='35' compileSdkVersionCodename='15'
sdkVersion:'21'
targetSdkVersion:'34'
uses-permission: name='android.permission.WRITE_EXTERNAL_STORAGE'
uses-permission: name='android.permission.READ_EXTERNAL_STORAGE'
application: label='' icon=''
feature-group: label=''
uses-feature: name='android.hardware.faketouch'
uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps'
other-activities
supports-screens: 'small' 'normal' 'large' 'xlarge'
supports-any-density: 'true'
locales: '--_--'
densities: '160' '65535'
native-code: 'arm64-v8a' 'armeabi-v7a' 'x86' 'x86_64'
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.alexvanyo.composelife.algorithm

import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.compose.ui.unit.IntOffset
import com.alexvanyo.composelife.dispatchers.ComposeLifeDispatchers
import com.alexvanyo.composelife.dispatchers.TestComposeLifeDispatchers
import com.alexvanyo.composelife.model.CellState
import com.alexvanyo.composelife.patterns.GameOfLifeTestPattern
import com.alexvanyo.composelife.patterns.GosperGliderGunPattern
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import com.google.testing.junit.testparameterinjector.TestParameterValuesProvider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.Rule
import org.junit.runner.RunWith
import kotlin.test.Test

@RunWith(TestParameterInjector::class)
class GameOfLifeAlgorithmBenchmarks {

@get:Rule
val benchmarkRule = BenchmarkRule()

class GameOfLifeAlgorithmFactory(
private val algorithmName: String,
val factory: (dispatchers: ComposeLifeDispatchers) -> GameOfLifeAlgorithm,
) {
override fun toString(): String = algorithmName

class Provider : TestParameterValuesProvider() {
override fun provideValues(context: Context?) =
listOf(
GameOfLifeAlgorithmFactory("Naive Algorithm") {
NaiveGameOfLifeAlgorithm(it)
},
GameOfLifeAlgorithmFactory("HashLife Algorithm") {
HashLifeAlgorithm(it)
},
)
}
}

class CellStateMapper(
private val name: String,
val mapper: (CellState) -> CellState,
) {
override fun toString(): String = name

class Provider : TestParameterValuesProvider() {
override fun provideValues(context: Context?) =
listOf(
CellStateMapper("Identity") { cellState ->
cellState
},
CellStateMapper("Flip across x-axis") { cellState ->
CellState(cellState.aliveCells.map { cell -> IntOffset(cell.x, -cell.y) }.toSet())
},
CellStateMapper("Flip across y-axis") { cellState ->
CellState(cellState.aliveCells.map { cell -> IntOffset(-cell.x, cell.y) }.toSet())
},
CellStateMapper("Flip across x = y") { cellState ->
CellState(cellState.aliveCells.map { cell -> IntOffset(cell.y, cell.x) }.toSet())
},
CellStateMapper("Translate by an arbitrary amount") { cellState ->
CellState(cellState.aliveCells.map { cell -> cell + IntOffset(157, 72) }.toSet())
},
)
}
}

private val testPattern: GameOfLifeTestPattern = GosperGliderGunPattern

@TestParameter(valuesProvider = GameOfLifeAlgorithmFactory.Provider::class)
lateinit var algorithmFactory: GameOfLifeAlgorithmFactory

@TestParameter(valuesProvider = CellStateMapper.Provider::class)
lateinit var cellStateMapper: CellStateMapper

@Test
fun generations_100() {
@OptIn(ExperimentalCoroutinesApi::class)
val testDispatcher = UnconfinedTestDispatcher()

benchmarkRule.measureRepeated {
runBlocking {
val algorithm = runWithTimingDisabled {
algorithmFactory.factory(
TestComposeLifeDispatchers(
generalTestDispatcher = testDispatcher,
cellTickerTestDispatcher = testDispatcher,
),
)
}
val originalCellState = runWithTimingDisabled {
cellStateMapper.mapper(testPattern.seedCellState)
}

algorithm.computeGenerationsWithStep(
originalCellState = originalCellState,
step = 1,
)
.take(100)
.collect {}
}
}
}
}
17 changes: 17 additions & 0 deletions algorithm-benchmark/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest />
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Local API version for Robolectric. This will be overridden in CI for parameterization.
sdk=35
application=com.alexvanyo.composelife.test.TestInjectApplication
28 changes: 28 additions & 0 deletions algorithm-benchmark/staging-proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

-keep,allowobfuscation class androidx.compose.runtime.MonotonicFrameClock {
*;
}
-keep,allowobfuscation class androidx.compose.ui.platform.InfiniteAnimationPolicy {
*;
}
49 changes: 49 additions & 0 deletions algorithm-benchmark/staging-test-proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

# Added due to dependency on Material via accessibility-test-framework
-dontwarn androidx.appcompat.graphics.drawable.DrawableWrapper

-dontwarn androidx.test.platform.app.AppComponentFactoryRegistry
-dontwarn androidx.test.platform.concurrent.DirectExecutor
-dontwarn androidx.work.Data$Builder
-dontwarn androidx.work.Data
-dontwarn androidx.work.ForegroundInfo
-dontwarn androidx.work.ListenableWorker$Result
-dontwarn androidx.work.OneTimeWorkRequest$Builder
-dontwarn androidx.work.OneTimeWorkRequest
-dontwarn androidx.work.Operation
-dontwarn androidx.work.OutOfQuotaPolicy
-dontwarn androidx.work.WorkManager
-dontwarn androidx.work.WorkRequest$Builder
-dontwarn androidx.work.WorkRequest
-dontwarn androidx.work.Worker
-dontwarn androidx.work.WorkerParameters
-dontwarn androidx.work.impl.utils.futures.SettableFuture
-dontwarn androidx.work.multiprocess.RemoteListenableWorker
-dontwarn java.beans.BeanInfo
-dontwarn java.beans.FeatureDescriptor
-dontwarn java.beans.IntrospectionException
-dontwarn java.beans.Introspector
-dontwarn java.beans.PropertyDescriptor
-dontwarn java.lang.reflect.AnnotatedType
-dontwarn javax.lang.model.element.Modifier
1 change: 1 addition & 0 deletions build-logic/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}

Expand Down
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ androidx-activityCompose = { group = "androidx.activity", name = "activity-compo
androidx-annotation = { group = "androidx.annotation", name = "annotation", version.ref = "androidxAnnotation" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppcompat" }
androidx-benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "androidxBenchmark" }
androidx-benchmark-micro-junit4 = { group = "androidx.benchmark", name = "benchmark-junit4", version.ref = "androidxBenchmark" }
androidx-compose-animation = { group = "androidx.compose.animation", name = "animation" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom-alpha", version.ref = "androidxComposeBomAlpha" }
androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" }
Expand Down Expand Up @@ -260,9 +261,11 @@ convention-kotlinMultiplatformCompose = { id = "com.alexvanyo.composelife.kotlin
convention-mergeJacoco = { id = "com.alexvanyo.composelife.mergeJacoco" }
android-lint = { id = "com.android.lint", version.ref = "androidGradlePlugin" }
androidx-baselineProfile = { id = "androidx.baselineprofile", version.ref = "androidxBenchmark" }
androidx-benchmark = { id = "androidx.benchmark", version.ref = "androidxBenchmark" }
compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
gradleDependenciesSorter = { id = "com.squareup.sort-dependencies", version.ref = "gradleDependenciesSorter" }
keeper = { id = "com.slack.keeper" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" }
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
Expand Down
Loading

0 comments on commit 925d340

Please sign in to comment.