Skip to content

Commit

Permalink
Added shared prefs edit benchmark (commit vs apply)
Browse files Browse the repository at this point in the history
  • Loading branch information
techyourchance committed Jul 4, 2024
1 parent bf3823e commit becc656
Show file tree
Hide file tree
Showing 13 changed files with 502 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.techyourchance.android.benchmarks.shared_prefs_write

import android.content.Context
import android.content.SharedPreferences
import com.techyourchance.android.common.datetime.DateTimeProvider
import com.techyourchance.android.common.maths.LinearFitCalculator
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.withContext
import java.util.concurrent.Executors
import javax.inject.Inject

class SharedPrefsWriteBenchmarkUseCase @Inject constructor(
private val context: Context,
private val dateTimeProvider: DateTimeProvider,
private val linearFitCalculator: LinearFitCalculator,
) {

data class Result(
val resultWithCommit: SharedPrefsWriteResult,
val resultWithApply: SharedPrefsWriteResult,
)

private val coroutinesDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()

suspend fun runBenchmark(valueToWrite: String): Result {
return withContext(coroutinesDispatcher) {

val sharedPrefs = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)

val commitRawTimingsNano = runBenchmarkWithWriteFunction(sharedPrefs, valueToWrite) {
it.commit()
}

val applyRawTimingsNano = runBenchmarkWithWriteFunction(sharedPrefs, valueToWrite) {
it.apply()
}

val resultWithCommit = computeResult(commitRawTimingsNano)

val resultWithApply = computeResult(applyRawTimingsNano)

Result(resultWithCommit, resultWithApply)
}
}

private fun runBenchmarkWithWriteFunction(
sharedPrefs: SharedPreferences,
sharedPrefValue: String,
editorWriteFunction: (SharedPreferences.Editor) -> Unit
): MutableMap<Int, MutableMap<Int, Long>> {

var sharedPrefsEditor: SharedPreferences.Editor? = null
var sharedPrefKey = ""
var startNano = 0L
var endNano = 0L

val rawTimingsNano = mutableMapOf<Int, MutableMap<Int, Long>>()

for (benchmarkIteration in 0 until NUM_ITERATIONS) {
sharedPrefs.edit().clear().commit()

rawTimingsNano[benchmarkIteration] = mutableMapOf()
for (prefEntryIndex in 0 until NUM_OF_WRITES_PER_ITERATION) {

sharedPrefKey =
PREF_KEY_PREFIX + prefEntryIndex.toString().padEnd(NUM_OF_WRITES_PER_ITERATION.toString().length)

startNano = dateTimeProvider.getNanoTime()
sharedPrefsEditor = sharedPrefs.edit().putString(sharedPrefKey, sharedPrefValue)
editorWriteFunction(sharedPrefsEditor)
endNano = dateTimeProvider.getNanoTime()

rawTimingsNano[benchmarkIteration]!![prefEntryIndex] = endNano - startNano

}
}
return rawTimingsNano
}


private fun computeResult(rawTimingsNano: Map<Int, MutableMap<Int, Long>>): SharedPrefsWriteResult {
val averageWriteDurationsWithCommit = computeAverageWriteDurationsNano(rawTimingsNano)
return SharedPrefsWriteResult(
averageWriteDurationsWithCommit,
linearFitCalculator.calculateLinearFit(
averageWriteDurationsWithCommit.map { entry -> Pair(entry.key.toDouble(), entry.value.toDouble()) }
)
)
}

private fun computeAverageWriteDurationsNano(sharedPrefsWriteTimings: Map<Int, Map<Int, Long>>): Map<Int, Long> {
val sumsOfWriteTimes = computeSumsWithInternalKeys(sharedPrefsWriteTimings)
return sumsOfWriteTimes.mapValues { entry ->
(entry.value.toDouble() / sharedPrefsWriteTimings.size).toLong()
}
}

private fun computeSumsWithInternalKeys(externalMap: Map<Int, Map<Int, Long>>): Map<Int, Long> {
val sums = mutableMapOf<Int, Long>()
for ((externalKey, internalMap) in externalMap) {
for ((internalKey, value) in internalMap) {
sums[internalKey] = sums.getOrDefault(internalKey, 0) + value
}
}
return sums
}

companion object {

private const val SHARED_PREFS_NAME = "benchmark_shared_prefs"
private const val NUM_OF_WRITES_PER_ITERATION = 100
private const val PREF_KEY_PREFIX = "key"
private const val NUM_ITERATIONS = 10
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.techyourchance.android.benchmarks.shared_prefs_write

import com.techyourchance.android.common.maths.LinearFitCoefficients

data class SharedPrefsWriteResult(
val entryIndexToAverageEditDurationsNano: Map<Int, Long>,
val linearFitCoefficients: LinearFitCoefficients,
)
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.techyourchance.android.screens.animations.dotsprogress.DotsProgressAn
import com.techyourchance.android.screens.benchmarks.backgroundtasksmemorybenchmark.BackgroundTasksMemoryBenchmarkFragment
import com.techyourchance.android.screens.benchmarks.benchmarkslist.BenchmarksListFragment
import com.techyourchance.android.screens.benchmarks.backgroundtasksstartupbenchmark.BackgroundTasksStartupBenchmarkFragment
import com.techyourchance.android.screens.benchmarks.sharedprefs.SharedPrefsBenchmarkFragment
import com.techyourchance.android.screens.composeoverlay.ComposeOverlayFragment
import com.techyourchance.android.screens.composenavbottombar.ComposeNavBottomBarActivity
import com.techyourchance.android.screens.handlerlooper.HandlerLooperFragment
Expand Down Expand Up @@ -54,6 +55,7 @@ interface ControllerComponent {
fun inject(fragment: ComposeOverlayFragment)
fun inject(fragment: AnimatedMessagesFragment)
fun inject(fragment: HandlerLooperFragment)
fun inject(fragment: SharedPrefsBenchmarkFragment)

// Dialogs
fun inject(dialog: PromptDialog)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.techyourchance.android.common.random

import javax.inject.Inject
import java.lang.StringBuilder
import java.security.SecureRandom
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock

class RandomStringsGenerator @Inject constructor() {

fun getRandomAlphanumericString(length: Int): String {
return getRandomStringFromAlphabet(ALPHANUMERIC, length)
}

fun getRandomNumericString(length: Int): String {
return getRandomStringFromAlphabet(NUMERIC, length)
}

private fun getRandomStringFromAlphabet(alphabet: String, length: Int): String {
val sb = StringBuilder(length)
for (i in 0 until length) {
var position: Int
Companion.LOCK.withLock { // thread-safe protection of SecureRandom (just in case)
position = SECURE_RANDOM.nextInt(alphabet.length)
}
sb.append(alphabet[position])
}
return sb.toString()
}

companion object {
private val SECURE_RANDOM = SecureRandom()
private val LOCK = ReentrantLock()

private const val ALPHANUMERIC = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
private const val NUMERIC = "0123456789"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ class BenchmarksListFragment : BaseFragment(), BenchmarksListViewMvc.Listener {
getString(R.string.screen_background_tasks_memory_benchmark),
ScreenSpec.BackgroundTasksMemoryBenchmark()
),
FromBenchmarksListDestination(
getString(R.string.screen_shared_prefs_benchmark),
ScreenSpec.SharedPrefsBenchmark
),
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.techyourchance.android.screens.benchmarks.sharedprefs

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.techyourchance.android.benchmarks.shared_prefs_write.SharedPrefsWriteBenchmarkUseCase
import com.techyourchance.android.common.random.RandomStringsGenerator
import com.techyourchance.android.screens.common.ScreensNavigator
import com.techyourchance.android.screens.common.dialogs.DialogsNavigator
import com.techyourchance.android.screens.common.fragments.BaseFragment
import com.techyourchance.android.screens.common.mvcviews.ViewMvcFactory
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import javax.inject.Inject

class SharedPrefsBenchmarkFragment : BaseFragment(), SharedPrefsBenchmarkViewMvc.Listener {

@Inject lateinit var viewMvcFactory: ViewMvcFactory
@Inject lateinit var dialogsNavigator: DialogsNavigator
@Inject lateinit var screensNavigator: ScreensNavigator
@Inject lateinit var sharedPrefsWriteBenchmarkUseCase: SharedPrefsWriteBenchmarkUseCase
@Inject lateinit var randomStringsGenerator: RandomStringsGenerator

private lateinit var viewMvc: SharedPrefsBenchmarkViewMvc

private var startBenchmark = false

private var benchmarkJob: Job? = null

override fun onCreate(savedInstanceState: Bundle?) {
controllerComponent.inject(this)
super.onCreate(savedInstanceState)
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
viewMvc = viewMvcFactory.newSharedPrefsBenchmarkViewMvc(container)
return viewMvc.getRootView()
}

override fun onStart() {
super.onStart()
viewMvc.registerListener(this)
viewMvc.showBenchmarkStopped()
if (startBenchmark) {
startBenchmark()
}
}

override fun onStop() {
super.onStop()
viewMvc.unregisterListener(this)
benchmarkJob?.cancel()
}

override fun onToggleBenchmarkClicked() {
benchmarkJob?.let {
if (it.isActive) {
it.cancel()
} else {
startBenchmark()
}
} ?: startBenchmark()
}

private fun startBenchmark() {
benchmarkJob = coroutineScope.launch {
try {
viewMvc.showBenchmarkStarted()
val valueToWrite = randomStringsGenerator.getRandomAlphanumericString(PREF_VALUE_LENGTH)
val result = sharedPrefsWriteBenchmarkUseCase.runBenchmark(valueToWrite)
viewMvc.bindBenchmarkResults(
PREF_VALUE_LENGTH,
result.resultWithCommit,
result.resultWithApply
)
} finally {
viewMvc.showBenchmarkStopped()
}
}
}

override fun onBackClicked() {
screensNavigator.navigateBack()
}

companion object {

private const val PREF_VALUE_LENGTH = 100

fun newInstance(): SharedPrefsBenchmarkFragment {
val args = Bundle(3)
val fragment = SharedPrefsBenchmarkFragment()
fragment.arguments = args
return fragment
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.techyourchance.android.screens.benchmarks.sharedprefs

import com.techyourchance.android.benchmarks.shared_prefs_write.SharedPrefsWriteResult
import com.techyourchance.android.screens.common.mvcviews.BaseObservableViewMvc

abstract class SharedPrefsBenchmarkViewMvc: BaseObservableViewMvc<SharedPrefsBenchmarkViewMvc.Listener>() {

interface Listener {
fun onBackClicked()
fun onToggleBenchmarkClicked()
}

abstract fun bindBenchmarkResults(
prefValueLength: Int,
resultWithCommit: SharedPrefsWriteResult,
resultWithApply: SharedPrefsWriteResult,
)
abstract fun showBenchmarkStarted()
abstract fun showBenchmarkStopped()
}
Loading

0 comments on commit becc656

Please sign in to comment.