Adds a large benchmark test for duplicate events.

pull/810/head
Vitor Pamplona 2024-03-18 18:06:37 -04:00
rodzic 92d9c682f8
commit 55a6f8829e
5 zmienionych plików z 630 dodań i 6 usunięć

Wyświetl plik

@ -9,7 +9,11 @@ android {
compileSdk 34
defaultConfig {
minSdk 26
targetSdk 34
// Enable measuring on an emulator, or devices with low battery
testInstrumentationRunner 'androidx.benchmark.junit4.AndroidBenchmarkRunner'
testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "EMULATOR,LOW-BATTERY"
}
@ -21,12 +25,7 @@ android {
jvmTarget = '17'
}
defaultConfig {
minSdk 26
targetSdk 34
testInstrumentationRunner 'androidx.benchmark.junit4.AndroidBenchmarkRunner'
}
sourceSets.androidTest.assets.srcDirs += ["../quartz/src/androidTest/assets"]
testBuildType = "benchmark"
buildTypes {

Wyświetl plik

@ -0,0 +1,397 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.benchmark
import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.amethyst.commons.data.LargeCache
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.events.Event
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentSkipListMap
import java.util.function.BiConsumer
import java.util.function.Consumer
import java.util.zip.GZIPInputStream
open class BaseCacheBenchmark {
fun getEventDB(): List<Event> {
// This file includes duplicates
val fullDBInputStream = getInstrumentation().context.assets.open("nostr_vitor_startup_data.json")
return Event.mapper.readValue<ArrayList<Event>>(
GZIPInputStream(fullDBInputStream),
)
}
fun getConcurrentSkipList(db: List<Event>): ConcurrentSkipListMap<HexKey, Event> {
val cache = ConcurrentSkipListMap<HexKey, Event>()
db.forEach {
cache.put(it.id, it)
}
return cache
}
fun getConcurrentHashMap(db: List<Event>): ConcurrentHashMap<HexKey, Event> {
val cache = ConcurrentHashMap<HexKey, Event>()
db.forEach {
cache.put(it.id, it)
}
return cache
}
fun getRegularHashMap(db: List<Event>): HashMap<HexKey, Event> {
val cache = HashMap<HexKey, Event>()
db.forEach {
cache.put(it.id, it)
}
return cache
}
fun getLargeCache(db: List<Event>): LargeCache<HexKey, Event> {
val cache = LargeCache<HexKey, Event>()
db.forEach { event ->
cache.getOrCreate(event.id) {
event
}
}
return cache
}
fun hasId(event: Event) {
assertTrue(event.id.isNotEmpty())
}
val consumer =
Consumer<Event> {
hasId(it)
}
val biconsumer =
BiConsumer<HexKey, Event> { hex, event ->
hasId(event)
}
}
@RunWith(AndroidJUnit4::class)
class CacheLoadingBenchmark : BaseCacheBenchmark() {
@get:Rule val benchmarkRule = BenchmarkRule()
@Test
fun loadConcurrentSkipList() {
val db = getEventDB()
benchmarkRule.measureRepeated { getConcurrentSkipList(db) }
}
@Test
fun loadCountDuplicates() {
val db = getEventDB().distinctBy { it.id }.toList()
}
@Test
fun loadConcurrentHashMap() {
val db = getEventDB()
benchmarkRule.measureRepeated { getConcurrentHashMap(db) }
}
@Test
fun loadRegularHashMap() {
val db = getEventDB()
benchmarkRule.measureRepeated { getRegularHashMap(db) }
}
@Test
fun loadLargeCache() {
val db = getEventDB()
benchmarkRule.measureRepeated { getLargeCache(db) }
}
}
@RunWith(AndroidJUnit4::class)
class CommonForEachBenchmark : BaseCacheBenchmark() {
@get:Rule
val benchmarkRule = BenchmarkRule()
@Test
fun benchForEachRegularList() {
val db = getEventDB().distinctBy { it.id }.toList()
benchmarkRule.measureRepeated { db.forEach { hasId(it) } }
}
@Test
fun benchForEachConsumerList() {
val db = getEventDB().distinctBy { it.id }.toList()
benchmarkRule.measureRepeated { db.forEach(consumer) }
}
@Test
fun forEachConcurrentSkipList() {
val cache = getConcurrentSkipList(getEventDB())
benchmarkRule.measureRepeated { cache.forEach { hasId(it.value) } }
}
@Test
fun forEachConcurrentHashMap() {
val cache = getConcurrentHashMap(getEventDB())
benchmarkRule.measureRepeated { cache.forEach { hasId(it.value) } }
}
@Test
fun forEachRegularHashMap() {
val cache = getRegularHashMap(getEventDB())
benchmarkRule.measureRepeated { cache.forEach { hasId(it.value) } }
}
@Test
fun consumerForEachConcurrentSkipList() {
val cache = getConcurrentSkipList(getEventDB())
benchmarkRule.measureRepeated { cache.forEach(biconsumer) }
}
@Test
fun consumerForEachConcurrentHashMap() {
val cache = getConcurrentHashMap(getEventDB())
benchmarkRule.measureRepeated { cache.forEach(biconsumer) }
}
@Test
fun consumerForEachRegularHashMap() {
val cache = getRegularHashMap(getEventDB())
benchmarkRule.measureRepeated { cache.forEach(biconsumer) }
}
@Test
fun consumerForEachLargeCache() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.forEach(biconsumer) }
}
}
@RunWith(AndroidJUnit4::class)
class CommonMapBenchmark : BaseCacheBenchmark() {
@get:Rule
val benchmarkRule = BenchmarkRule()
@Test
fun benchMapRegularList() {
val db = getEventDB().distinctBy { it.id }.toList()
benchmarkRule.measureRepeated { db.map { it.id } }
}
@Test
fun consumerMapConcurrentSkipList() {
val cache = getConcurrentSkipList(getEventDB())
benchmarkRule.measureRepeated { cache.map { it.value.id } }
}
@Test
fun consumerMapConcurrentHashMap() {
val cache = getConcurrentHashMap(getEventDB())
benchmarkRule.measureRepeated { cache.map { it.value.id } }
}
@Test
fun consumerMapLargeCache() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.map { key, item -> item.id } }
}
}
@RunWith(AndroidJUnit4::class)
class BiggerForEachBenchmark : BaseCacheBenchmark() {
@get:Rule val benchmarkRule = BenchmarkRule()
@Test
fun benchForEachRegularList() {
val db = getEventDB().distinctBy { it.id }.toList()
benchmarkRule.measureRepeated { db.forEach { hasId(it) } }
}
@Test
fun benchForEachConsumerList() {
val db = getEventDB().distinctBy { it.id }.toList()
benchmarkRule.measureRepeated { db.forEach(consumer) }
}
@Test
fun forEachConcurrentSkipList() {
val cache = getConcurrentSkipList(getEventDB())
benchmarkRule.measureRepeated { cache.forEach { hasId(it.value) } }
}
@Test
fun forEachConcurrentHashMap() {
val cache = getConcurrentHashMap(getEventDB())
benchmarkRule.measureRepeated { cache.forEach { hasId(it.value) } }
}
@Test
fun forEachRegularHashMap() {
val cache = getRegularHashMap(getEventDB())
benchmarkRule.measureRepeated { cache.forEach { hasId(it.value) } }
}
@Test
fun consumerForEachConcurrentSkipList() {
val cache = getConcurrentSkipList(getEventDB())
benchmarkRule.measureRepeated { cache.forEach(biconsumer) }
}
@Test
fun consumerForEachConcurrentHashMap() {
val cache = getConcurrentHashMap(getEventDB())
benchmarkRule.measureRepeated { cache.forEach(biconsumer) }
}
@Test
fun consumerForEachRegularHashMap() {
val cache = getRegularHashMap(getEventDB())
benchmarkRule.measureRepeated { cache.forEach(biconsumer) }
}
@Test
fun consumerForEachLargeCache() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.forEach(biconsumer) }
}
@Test
fun valuesConcurrentSkipList() {
val cache = getConcurrentSkipList(getEventDB())
benchmarkRule.measureRepeated { cache.values.forEach { hasId(it) } }
}
@Test
fun valuesConcurrentHashMap() {
val cache = getConcurrentHashMap(getEventDB())
benchmarkRule.measureRepeated { cache.values.forEach { hasId(it) } }
}
@Test
fun valuesRegularHashMap() {
val cache = getRegularHashMap(getEventDB())
benchmarkRule.measureRepeated { cache.values.forEach { hasId(it) } }
}
@Test
fun consumerValuesConcurrentSkipList() {
val cache = getConcurrentSkipList(getEventDB())
benchmarkRule.measureRepeated { cache.values.forEach(consumer) }
}
@Test
fun consumerValuesConcurrentHashMap() {
val cache = getConcurrentHashMap(getEventDB())
benchmarkRule.measureRepeated { cache.values.forEach(consumer) }
}
@Test
fun consumerValuesRegularHashMap() {
val cache = getRegularHashMap(getEventDB())
benchmarkRule.measureRepeated { cache.values.forEach(consumer) }
}
@Test
fun iterableConcurrentSkipList() {
val cache = getConcurrentSkipList(getEventDB())
benchmarkRule.measureRepeated {
with(cache.iterator()) {
while (hasNext()) {
hasId(next().value)
}
}
}
}
@Test
fun iterableConcurrentHashMap() {
val cache = getConcurrentHashMap(getEventDB())
benchmarkRule.measureRepeated {
with(cache.iterator()) {
while (hasNext()) {
hasId(next().value)
}
}
}
}
@Test
fun iterableRegularHashMap() {
val cache = getRegularHashMap(getEventDB())
benchmarkRule.measureRepeated {
with(cache.iterator()) {
while (hasNext()) {
hasId(next().value)
}
}
}
}
@Test
fun iterableValuesConcurrentSkipList() {
val cache = getConcurrentSkipList(getEventDB())
benchmarkRule.measureRepeated {
with(cache.values.iterator()) {
while (hasNext()) {
hasId(next())
}
}
}
}
@Test
fun iterableValuesConcurrentHashMap() {
val cache = getConcurrentHashMap(getEventDB())
benchmarkRule.measureRepeated {
with(cache.values.iterator()) {
while (hasNext()) {
hasId(next())
}
}
}
}
@Test
fun iterableValuesRegularHashMap() {
val cache = getRegularHashMap(getEventDB())
benchmarkRule.measureRepeated {
with(cache.values.iterator()) {
while (hasNext()) {
hasId(next())
}
}
}
}
}

Wyświetl plik

@ -0,0 +1,205 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.benchmark
import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.amethyst.commons.data.LargeCache
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.events.Event
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.util.Arrays
import java.util.function.Consumer
import java.util.zip.GZIPInputStream
open class BaseLargeCacheBenchmark {
fun getEventDB(): List<Event> {
// This file includes duplicates
val fullDBInputStream = getInstrumentation().context.assets.open("nostr_vitor_startup_data.json")
return Event.mapper.readValue<ArrayList<Event>>(
GZIPInputStream(fullDBInputStream),
)
}
fun getLargeCache(db: List<Event>): LargeCache<HexKey, Event> {
val cache = LargeCache<HexKey, Event>()
db.forEach {
cache.getOrCreate(it.id) { key ->
it
}
}
return cache
}
fun hasId(event: Event) {
assertTrue(event.id.isNotEmpty())
}
val consumer =
Consumer<Event> {
hasId(it)
}
}
@RunWith(AndroidJUnit4::class)
class LargeCacheForEachBenchmark : BaseLargeCacheBenchmark() {
@get:Rule val benchmarkRule = BenchmarkRule()
// 191,353 ns 0 allocs Trace EMULATOR_LargeCacheForEachBenchmark.benchForEachConsumerList
@Test
fun benchForEachConsumerList() {
val db = getEventDB().distinctBy { it.id }.toList()
benchmarkRule.measureRepeated { db.forEach(consumer) }
}
// 245,319 ns 1 allocs Trace EMULATOR_LargeCacheForEachBenchmark.benchForEachRegularList
@Test
fun benchForEachRegularList() {
val db = getEventDB().distinctBy { it.id }.toList()
benchmarkRule.measureRepeated { db.forEach { hasId(it) } }
}
// 435,097 ns 1 allocs Trace EMULATOR_LargeCacheForEachBenchmark.forEach
@Test
fun forEach() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.forEach { key, it -> hasId(it) } }
}
// 525,329 ns 18 allocs Trace EMULATOR_LargeCacheForEachBenchmark.filterKind1List
@Test
fun filterKind1List() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.filter { key, it -> it.kind == 1 } }
}
// 690,323 ns 3581 allocs Trace EMULATOR_LargeCacheForEachBenchmark.filterKind1Set
@Test
fun filterKind1Set() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.filterIntoSet { key, it -> it.kind == 1 } }
}
// 641,179 ns 23 allocs Trace EMULATOR_LargeCacheForEachBenchmark.mapToSigs
@Test
fun mapToSigs() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.map { key, it -> it.sig } }
}
// 590,930 ns 23 allocs Trace EMULATOR_LargeCacheForEachBenchmark.mapNotNullTagList
@Test
fun mapNotNullTagList() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.mapNotNull { key, it -> it.tags.firstOrNull() } }
}
// HashSet: 1,817,833 ns 30632 allocs Trace EMULATOR_LargeCacheForEachBenchmark.mapNotNullTagSet
// LinkedHashSet: 2,057,674 ns 30633 allocs Trace EMULATOR_LargeCacheForEachBenchmark.mapNotNullTagSet
@Test
fun mapNotNullTagSet() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.mapNotNullIntoSet { key, it -> it.tags.firstOrNull() } }
}
// 2,619,604 ns 93505 allocs Trace EMULATOR_LargeCacheForEachBenchmark.mapFlattenTagList
@Test
fun mapFlattenTagList() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.mapFlatten { key, it -> it.tags.asList() } }
}
// ----
// 4,802,623 ns 114928 allocs Trace EMULATOR_LargeCacheForEachBenchmark.mapFlattenTagSetAsList
@Test
fun mapFlattenTagSetAsList() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.mapFlattenIntoSet { key, it -> it.tags.asList() } }
}
// 5,695,432 ns 146089 allocs Trace EMULATOR_LargeCacheForEachBenchmark.mapFlattenTagSetArraysAsList
@Test
fun mapFlattenTagSetArraysAsList() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.mapFlattenIntoSet { key, it -> Arrays.asList(*it.tags) } }
}
// 7,008,496 ns 176161 allocs Trace EMULATOR_LargeCacheForEachBenchmark.mapFlattenTagSetListOf
@Test
fun mapFlattenTagSetListOf() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.mapFlattenIntoSet { key, it -> listOf(*it.tags) } }
}
// 7,032,714 ns 193834 allocs Trace EMULATOR_LargeCacheForEachBenchmark.mapFlattenTagSetToList
@Test
fun mapFlattenTagSetToList() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.mapFlattenIntoSet { key, it -> it.tags.toList() } }
}
// ----
// 467,227 ns 1 allocs Trace EMULATOR_LargeCacheForEachBenchmark.sumOfKinds
@Test
fun sumOfKinds() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.sumOf { key, it -> it.kind } }
}
// 458,998 ns 1 allocs Trace EMULATOR_LargeCacheForEachBenchmark.sumOfKindLong
@Test
fun sumOfKindLong() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.sumOfLong { key, it -> it.createdAt } }
}
// 1,021,368 ns 11683 allocs Trace EMULATOR_LargeCacheForEachBenchmark.groupByKind
@Test
fun groupByKind() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.groupBy { key, it -> it.kind } }
}
// 1,133,156 ns 39899 allocs Trace EMULATOR_LargeCacheForEachBenchmark.countByGroupKind
@Test
fun countByGroupKind() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.countByGroup { key, it -> it.kind } }
}
// 428,641 ns 1 allocs Trace EMULATOR_LargeCacheForEachBenchmark.countNotEmptyTags
@Test
fun countNotEmptyTags() {
val cache = getLargeCache(getEventDB())
benchmarkRule.measureRepeated { cache.count { key, it -> it.tags.isNotEmpty() } }
}
}

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -30,6 +30,7 @@ import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import java.io.InputStreamReader
import java.util.zip.GZIPInputStream
@RunWith(AndroidJUnit4::class)
class LargeDBSignatureCheck {
@ -49,6 +50,28 @@ class LargeDBSignatureCheck {
counter++
}
assertEquals(eventArray.size, counter)
}
@Test
fun insertStartupDatabase() =
runBlocking {
// This file includes duplicates
val fullDBInputStream = getInstrumentation().context.assets.open("nostr_vitor_startup_data.json")
val eventArray =
Event.mapper.readValue<ArrayList<Event>>(
GZIPInputStream(fullDBInputStream),
) as List<Event>
var counter = 0
eventArray.forEach {
if (it.sig != "") {
assertTrue(it.hasValidSignature())
}
counter++
}
assertEquals(eventArray.size, counter)
}
}