Skip to content

Instantly share code, notes, and snippets.

@lbialy
Last active September 16, 2023 17:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lbialy/e5eeb6ba251d1d2860cf40ea200db7d2 to your computer and use it in GitHub Desktop.
Save lbialy/e5eeb6ba251d1d2860cf40ea200db7d2 to your computer and use it in GitHub Desktop.
Early check whether Chimney can deal with mutable classes and whether it even makes sense to use localised mutability at all. Needs JMH.
//> using lib "io.github.martinhh::scalacheck-derived:0.4.1"
//> using lib "org.scalacheck::scalacheck:1.17.0"
//> using lib "io.scalaland::chimney:0.8.0-M1"
//> using lib "org.openjdk.jmh:jmh-core:1.37"
package local.mutability
import scala.collection.mutable
import io.scalaland.chimney.dsl.*
case class Entry(value: String)
case class Event(entry: Entry)
case class State(count: Int, validEntries: Set[Entry])
class MutState(var count: Int, var validEntries: mutable.Set[Entry])
def computeStateImmutable(state: State, events: Iterator[Event]): State =
events.foldLeft(state) { (state, event) =>
state.copy(
count = state.count + 1,
validEntries =
if event.entry.value.toLowerCase().contains("x") then
state.validEntries + event.entry
else state.validEntries
)
}
def computeStateMutableChimney(state: State, events: Iterator[Event]): State =
val mutState = state.transformInto[MutState]
events.foreach { event =>
mutState.count += 1
if event.entry.value.toLowerCase().contains("x") then
mutState.validEntries += event.entry
}
mutState.transformInto[State]
def computeStateMutableManual(state: State, events: Iterator[Event]): State =
val mutState = MutState(state.count, mutable.Set.from(state.validEntries))
events.foreach { event =>
mutState.count += 1
if event.entry.value.toLowerCase().contains("x") then
mutState.validEntries += event.entry
}
State(mutState.count, mutState.validEntries.toSet)
import org.scalacheck.*
import io.github.martinhh.derived.scalacheck.given
def genEvents: Iterator[Event] =
val genEvent = for entry <- Gen.alphaStr yield Event(Entry(entry))
Iterator.continually(genEvent.sample.get)
import java.util.concurrent.TimeUnit
import org.openjdk.jmh.annotations.*
@org.openjdk.jmh.annotations.State(Scope.Benchmark)
@Warmup(iterations = 5)
@Measurement(iterations = 3)
@BenchmarkMode(Array(Mode.SingleShotTime))
class Benchmarks:
val staticEvents = genEvents.take(1000000).toVector
@Benchmark
def immutable(): State =
computeStateImmutable(State(0, Set.empty), staticEvents.iterator)
@Benchmark
def mutableManual(): State =
computeStateMutableManual(State(0, Set.empty), staticEvents.iterator)
@Benchmark
def mutableChimney(): State =
computeStateMutableChimney(State(0, Set.empty), staticEvents.iterator)

Apparently TLABs are awesome, I guess?

Here's what I get on a 2021 MBP M1 Pro running with OpenJDK 19:

Benchmark                                     Mode  Cnt          Score        Error   Units
Benchmarks.immutable                            ss   15          1.367 ±      0.030    s/op
Benchmarks.immutable:gc.alloc.rate              ss   15        463.129 ±     10.164  MB/sec
Benchmarks.immutable:gc.alloc.rate.norm         ss   15  664039387.200 ± 461244.704    B/op
Benchmarks.immutable:gc.count                   ss   15         11.000               counts
Benchmarks.immutable:gc.time                    ss   15         79.000                   ms
Benchmarks.mutableChimney                       ss   15          1.638 ±      0.057    s/op
Benchmarks.mutableChimney:gc.alloc.rate         ss   15        119.821 ±      4.075  MB/sec
Benchmarks.mutableChimney:gc.alloc.rate.norm    ss   15  205715684.800 ± 113812.564    B/op
Benchmarks.mutableChimney:gc.count              ss   15          4.000               counts
Benchmarks.mutableChimney:gc.time               ss   15        175.000                   ms
Benchmarks.mutableManual                        ss   15          1.647 ±      0.072    s/op
Benchmarks.mutableManual:gc.alloc.rate          ss   15        119.267 ±      4.969  MB/sec
Benchmarks.mutableManual:gc.alloc.rate.norm     ss   15  205688534.400 ±  55861.563    B/op
Benchmarks.mutableManual:gc.count               ss   15          2.000               counts
Benchmarks.mutableManual:gc.time                ss   15         96.000                   ms

I'm not disregarding the idea that memory pressure is a problem but I wonder if it's a problem in tight loops like event folding onto state?

@lbialy
Copy link
Author

lbialy commented Sep 15, 2023

I forgot the command to run this: scala-cli run --jmh local-mutability-under-spectacle.scala -- -prof gc

@lbialy
Copy link
Author

lbialy commented Sep 16, 2023

or rather scala-cli run --jmh https://gist.github.com/lbialy/e5eeb6ba251d1d2860cf40ea200db7d2 -- -prof gc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment