Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kotlin: Experimental. Benchmark for coroutines (decoding with parallel tasks) #68

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Kotlin/demo/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ android {

defaultConfig {
applicationId "com.wolt.blurhash"
minSdkVersion 14
minSdkVersion 17
targetSdkVersion 29
versionCode 1
versionName "1.0"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
}

Expand All @@ -26,4 +27,6 @@ android {
dependencies {
implementation project(path: ':lib')
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
}
124 changes: 116 additions & 8 deletions Kotlin/demo/src/main/java/com/wolt/blurhashapp/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,24 +1,132 @@
package com.wolt.blurhashapp

import android.os.Bundle
import android.view.View
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import android.graphics.Bitmap
import android.os.*
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.*
import com.wolt.blurhashkt.BlurHashDecoder
import kotlinx.android.synthetic.main.activity_main.*
import java.util.concurrent.Executors
import kotlin.math.pow

class MainActivity : AppCompatActivity() {

private lateinit var vm: Vm

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val etInput: EditText = findViewById(R.id.etInput)
val ivResult: ImageView = findViewById(R.id.ivResult)
findViewById<View>(R.id.tvDecode).setOnClickListener {
vm = ViewModelProvider(this).get(Vm::class.java)
vm.observe(this, Observer {
when (it) {
"START" -> progressBar.visibility = VISIBLE
"END" -> progressBar.visibility = INVISIBLE
else -> {
ivResultBenchmark.append("\n$it")
ivResultBenchmark.scrollTo(0, ivResultBenchmark.layout.lineCount)
}
}
})
tvDecode.setOnClickListener {
val bitmap = BlurHashDecoder.decode(etInput.text.toString(), 20, 12)
ivResult.setImageBitmap(bitmap)
ivResultBenchmark.setText("")
vm.startBenchMark(etInput.text.toString())
}
}

}

/**
* Executes a function and return the time spent in milliseconds.
*/
private inline fun timed(function: () -> Unit): Long {
val start = SystemClock.elapsedRealtimeNanos()
function()
return SystemClock.elapsedRealtimeNanos() - start
}

private const val NANOS = 1000000.0

class Vm : ViewModel() {
private val liveData = MutableLiveData<String>()
private val executor = Executors.newSingleThreadExecutor()
private val handler = Handler(Looper.getMainLooper())

fun observe(owner: LifecycleOwner, observer: Observer<in String>) {
liveData.observe(owner, observer)
}

fun startBenchMark(blurHash: String) {
executor.execute {
notifyBenchmark("START")
}
executor.execute {
notifyBenchmark("-----------------------------------")
notifyBenchmark("Device: ${Build.MANUFACTURER} - ${Build.MODEL}")
notifyBenchmark("OS: Android ${Build.VERSION.CODENAME} - API ${Build.VERSION.SDK_INT}")
}
executor.execute {
notifyBenchmark("-----------------------------------")
}
for (tasks in 1..3) {
executor.execute {
notifyBenchmark("")
notifyBenchmark("-----------------------------------")
notifyBenchmark("Parallel tasks: $tasks")
notifyBenchmark("-----------------------------------")
}
for (size in 1..3) {
val width = 20 * 2.0.pow(size - 1).toInt()
val height = 12 * 2.0.pow(size - 1).toInt()
executor.execute {
notifyBenchmark("width: $width, height: $height")
}
for (imageCount in 0..2) {
executor.execute {
benchmark(10.0.pow(imageCount).toInt(), width, height, blurHash, useCache = true, tasks = tasks)
}
}
executor.execute {
notifyBenchmark("\n")
}
}
}
val s = "-----------------------------------\n"
executor.execute {
notifyBenchmark(s)
}
executor.execute {
notifyBenchmark("END")
}
}

private fun benchmark(max: Int, width: Int, height: Int, blurHash: String, useCache: Boolean, tasks: Int) {
notifyBenchmark("-> $max bitmaps")
var bmp: Bitmap? = null
BlurHashDecoder.clearCache()
val listOfTimes = ArrayList<Long>()
for (i in 1..max) {
listOfTimes.add(timed {
bmp = BlurHashDecoder.decode(blurHash, width, height, useCache = useCache, parallelTasks = tasks)
})
}
notifyBenchmark("<- ${listOfTimes.sum().millis().format()} ms, " +
"Avg: ${(listOfTimes.sum().millis() / max.toDouble()).format()} ms, " +
"Max: ${listOfTimes.max().millis().format()}, " +
"Min: ${listOfTimes.min().millis().format()}")
// log the bitmap size
println("bmp size: ${bmp?.byteCount}")
}

private fun notifyBenchmark(s: String) {
handler.post {
liveData.value = s
}
}
}

private fun Long?.millis() = (this?.toDouble() ?: 0.0) / NANOS
private fun Double.format() = "%.${2}f".format(this)
23 changes: 23 additions & 0 deletions Kotlin/demo/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
Expand Down Expand Up @@ -40,4 +41,26 @@
android:layout_marginTop="24dp"
android:adjustViewBounds="true" />

<ProgressBar
android:id="@+id/progressBar"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:layout_margin="4dp"
android:visibility="invisible"
tools:visibility="visible" />

<EditText
android:id="@+id/ivResultBenchmark"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:editable="false"
android:inputType="none"
android:scrollbars="vertical"
android:textIsSelectable="true"
android:textSize="16sp"
tools:text="0 ms" />


</LinearLayout>
5 changes: 5 additions & 0 deletions Kotlin/lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7'

androidTestImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test:runner:1.2.0'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.wolt.blurhashkt

import android.graphics.Bitmap
import com.wolt.blurhashkt.BlurHashDecoder.clearCache
import com.wolt.blurhashkt.BlurHashDecoder.decode
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import java.nio.ByteBuffer
import java.util.*


class BlurHashDecoderTest {
@Before
@Throws(Exception::class)
fun setUp() {
clearCache()
}

@Test
fun decode_smallImage_cacheEnabled_shouldGetTheSameBitmapInManyRequests() {
val bmp1 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 20, 12)!!
val bmp2 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 20, 12)!!
val bmp3 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 20, 12)!!
val bmp4 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 20, 12, parallelTasks = 2)!!
val bmp5 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 20, 12, parallelTasks = 3)!!

bmp1.assertEquals(bmp2)
bmp2.assertEquals(bmp3)
bmp3.assertEquals(bmp4)
bmp4.assertEquals(bmp5)
}

@Test
fun decode_smallImage_differentCache_shouldGetTheSameBitmapInManyRequests() {
val bmp1 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 20, 12)!!
val bmp2 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 20, 12, useCache = false)!!
val bmp3 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 20, 12)!!
val bmp4 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 20, 12, useCache = false, parallelTasks = 2)!!
val bmp5 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 20, 12, useCache = false, parallelTasks = 3)!!

bmp1.assertEquals(bmp2)
bmp2.assertEquals(bmp3)
bmp3.assertEquals(bmp4)
bmp4.assertEquals(bmp5)
}

@Test
fun decode_smallImage_cacheDisabled_shouldGetTheSameBitmapInManyRequests() {
val bmp1 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 20, 12, useCache = false)!!
val bmp2 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 20, 12, useCache = false)!!
val bmp3 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 20, 12, useCache = false)!!
val bmp4 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 20, 12, useCache = false, parallelTasks = 2)!!
val bmp5 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 20, 12, useCache = false, parallelTasks = 3)!!

bmp1.assertEquals(bmp2)
bmp2.assertEquals(bmp3)
bmp3.assertEquals(bmp4)
bmp4.assertEquals(bmp5)
}

@Test
fun decode_bigImage_cacheEnabled_shouldGetTheSameBitmapInManyRequests() {
val bmp1 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 100, 100)!!
val bmp2 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 100, 100)!!
val bmp3 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 100, 100)!!
val bmp4 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 100, 100, parallelTasks = 2)!!
val bmp5 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 100, 100, parallelTasks = 3)!!

bmp1.assertEquals(bmp2)
bmp2.assertEquals(bmp3)
bmp3.assertEquals(bmp4)
bmp4.assertEquals(bmp5)
}

@Test
fun decode_bigImage_differentCache_shouldGetTheSameBitmapInManyRequests() {
val bmp1 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 100, 100)!!
val bmp2 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 100, 100, useCache = false)!!
val bmp3 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 100, 100)!!
val bmp4 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 100, 100, useCache = false, parallelTasks = 2)!!
val bmp5 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 100, 100, useCache = false, parallelTasks = 3)!!

bmp1.assertEquals(bmp2)
bmp2.assertEquals(bmp3)
bmp3.assertEquals(bmp4)
bmp4.assertEquals(bmp5)
}

@Test
fun decode_bigImage_cacheDisabled_shouldGetTheSameBitmapInManyRequests() {
val bmp1 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 100, 100, useCache = false)!!
val bmp2 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 100, 100, useCache = false)!!
val bmp3 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 100, 100, useCache = false)!!
val bmp4 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 100, 100, useCache = false, parallelTasks = 2)!!
val bmp5 = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 100, 100, useCache = false, parallelTasks = 3)!!

bmp1.assertEquals(bmp2)
bmp2.assertEquals(bmp3)
bmp3.assertEquals(bmp4)
bmp4.assertEquals(bmp5)
}
}

fun Bitmap.assertEquals(bitmap2: Bitmap) {
val buffer1: ByteBuffer = ByteBuffer.allocate(height * rowBytes)
copyPixelsToBuffer(buffer1)
val buffer2: ByteBuffer = ByteBuffer.allocate(bitmap2.height * bitmap2.rowBytes)
bitmap2.copyPixelsToBuffer(buffer2)
val equals = Arrays.equals(buffer1.array(), buffer2.array())
assertTrue(equals)
}
Loading