Skip to content

Instantly share code, notes, and snippets.

@sriharshachilakapati
Created December 17, 2023 16:23
Show Gist options
  • Save sriharshachilakapati/c6c41c4780f469aa919e4fb04c3d25b9 to your computer and use it in GitHub Desktop.
Save sriharshachilakapati/c6c41c4780f469aa919e4fb04c3d25b9 to your computer and use it in GitHub Desktop.
Benchmark for performance in data class vs normal class and kotlin comparisons vs custom comparator
package com.goharsha.benchmarks
import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.Blackhole
import java.util.concurrent.TimeUnit
import java.util.PriorityQueue
import kotlin.comparisons.compareByDescending
import kotlin.comparisons.thenBy
@State(Scope.Benchmark)
@Warmup(iterations = 10)
@Threads(20)
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
open class FoodRatingsBenchMark {
private lateinit var foods: Array<String>
private lateinit var cuisines: Array<String>
private lateinit var ratings: IntArray
private val FOODS_PER_CUISINE = 1000
private val TOTAL_CUISINES = 100
@Setup
fun setUp() {
val allFoods = Array<String>(FOODS_PER_CUISINE) { generateRandomString() }
val allCuisines = Array<String>(TOTAL_CUISINES) { generateRandomString() }
val allRatings = IntArray(FOODS_PER_CUISINE) { generateRandomInt() }
foods = Array(FOODS_PER_CUISINE * TOTAL_CUISINES) { "" }
cuisines = Array(foods.size) { "" }
ratings = IntArray(foods.size)
for (cuisineIdx in 0 until TOTAL_CUISINES) {
val cuisine = allCuisines[cuisineIdx]
val baseIdx = cuisineIdx * FOODS_PER_CUISINE
for (idx in 0 until FOODS_PER_CUISINE) {
foods[baseIdx + idx] = "${cuisine}_${allFoods[idx]}"
cuisines[baseIdx + idx] = cuisine
ratings[baseIdx + idx] = allRatings[idx]
}
}
}
@Benchmark
fun foodRatingsKotlinComparisonsWithDataClass() {
val foodRatings = FoodRatings(
foods,
cuisines,
ratings,
compareByDescending(FoodInfo::rating).thenBy(FoodInfo::food),
::FoodInfoDataClass
)
doBenchmark(foodRatings)
}
@Benchmark
fun foodRatingsKotlinComparisonsWithNormalClass() {
val foodRatings = FoodRatings(
foods,
cuisines,
ratings,
compareByDescending(FoodInfo::rating).thenBy(FoodInfo::food),
::FoodInfoClass
)
doBenchmark(foodRatings)
}
@Benchmark
fun foodRatingsCustomComparatorWithDataClass() {
val comparator = Comparator<FoodInfo> { f1, f2 ->
if (f1.rating == f2.rating) {
f1.food.compareTo(f2.food)
} else {
f2.rating.compareTo(f1.rating)
}
}
val foodRatings = FoodRatings(
foods,
cuisines,
ratings,
comparator,
::FoodInfoDataClass
)
doBenchmark(foodRatings)
}
@Benchmark
fun foodRatingsCustomComparatorWithNormalClass() {
val comparator = Comparator<FoodInfo> { f1, f2 ->
if (f1.rating == f2.rating) {
f1.food.compareTo(f2.food)
} else {
f2.rating.compareTo(f1.rating)
}
}
val foodRatings = FoodRatings(
foods,
cuisines,
ratings,
comparator,
::FoodInfoClass
)
doBenchmark(foodRatings)
}
private fun doBenchmark(foodRatings: FoodRatings) {
for (cuisine in cuisines) {
foodRatings.highestRated(cuisine)
}
for (ratingIdx in ratings.lastIndex downTo 0) {
val foodIdx = foods.lastIndex - ratingIdx
foodRatings.changeRating(foods[foodIdx], ratings[ratingIdx])
}
for (cuisine in cuisines) {
foodRatings.highestRated(cuisine)
}
}
}
interface FoodInfo {
val food: String
val cuisine: String
val rating: Int
}
private class FoodInfoClass(
override val food: String,
override val cuisine: String,
override val rating: Int
) : FoodInfo
private data class FoodInfoDataClass(
override val food: String,
override val cuisine: String,
override val rating: Int
) : FoodInfo
class FoodRatings(
foods: Array<String>,
cuisines: Array<String>,
ratings: IntArray,
private val foodComparator: Comparator<FoodInfo>,
private val makeFoodInfo: (String, String, Int) -> FoodInfo
) {
private val foodInfoMap = mutableMapOf<String, FoodInfo>()
private val cuisineQueueMap = mutableMapOf<String, PriorityQueue<FoodInfo>>()
init {
for (idx in 0 until foods.size) {
val food = foods[idx]
val cuisine = cuisines[idx]
val rating = ratings[idx]
insertFood(makeFoodInfo(food, cuisine, rating))
}
}
private fun insertFood(foodInfo: FoodInfo) {
foodInfoMap[foodInfo.food] = foodInfo
cuisineQueueMap
.getOrPut(foodInfo.cuisine) { PriorityQueue(foodComparator) }
.add(foodInfo)
}
private fun removeFood(foodInfo: FoodInfo) {
foodInfoMap.remove(foodInfo.food)
cuisineQueueMap[foodInfo.cuisine]!!.remove(foodInfo)
}
fun changeRating(food: String, newRating: Int) {
val oldFoodInfo = foodInfoMap[food] ?: return
val newFoodInfo = makeFoodInfo(food, oldFoodInfo.cuisine, newRating)
removeFood(oldFoodInfo)
insertFood(newFoodInfo)
}
fun highestRated(cuisine: String): String =
cuisineQueueMap[cuisine]!!.peek()!!.food
}
Benchmark Mode Cnt Score Error Units
FoodRatingsBenchMark.foodRatingsCustomComparatorWithDataClass thrpt 10 20.260 ± 0.737 ops/s
FoodRatingsBenchMark.foodRatingsKotlinComparisonsWithDataClass thrpt 10 19.209 ± 0.591 ops/s
FoodRatingsBenchMark.foodRatingsKotlinComparisonsWithNormalClass thrpt 10 46.459 ± 1.392 ops/s
FoodRatingsBenchMark.foodRatingsCustomComparatorWithNormalClass thrpt 10 56.936 ± 1.433 ops/s
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment