Created
December 17, 2023 16:23
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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