Skip to content
Merged
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
16 changes: 16 additions & 0 deletions modules/bench/src/main/scala/doobie/bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,19 @@ d.b.t.batch_optimized avgt 2 2008.081 ns/op
d.b.t.copy_foldable avgt 2 306.674 ns/op
d.b.t.copy_stream avgt 2 382.481 ns/op
```

`bench/Jmh/run -wi 500 -i 1500 -f 1 doobie.bench.insert`
```
# JMH version: 1.37
# VM version: JDK 21.0.8, OpenJDK 64-Bit Server VM
Benchmark Mode Cnt Score Error Units
d.b.i.array_512 ss 1500 2108.146 ± 29.363 ns/op
d.b.i.array_1024 ss 1500 1282.125 ± 17.193 ns/op
d.b.i.array_2048 ss 1500 901.204 ± 27.426 ns/op
d.b.i.batch_512 ss 1500 3610.360 ± 49.533 ns/op
d.b.i.batch_1024 ss 1500 2824.224 ± 158.971 ns/op
d.b.i.batch_2048 ss 1500 2413.790 ± 15.661 ns/op
d.b.i.values_512 ss 1500 2661.017 ± 70.472 ns/op
d.b.i.values_1024 ss 1500 1930.406 ± 25.466 ns/op
d.b.i.values_2048 ss 1500 1759.005 ± 13.514 ns/op
```
141 changes: 141 additions & 0 deletions modules/bench/src/main/scala/doobie/bench/insert.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package doobie.bench

import cats.syntax.apply.*
import cats.syntax.foldable.*
import cats.syntax.functor.*
import doobie.free.connection.ConnectionIO
import doobie.syntax.string.*
import doobie.util.Write
import doobie.util.update.Update
import org.openjdk.jmh.annotations.*

import java.util.concurrent.TimeUnit

@BenchmarkMode(Array(Mode.SingleShotTime))
@OutputTimeUnit(TimeUnit.NANOSECONDS)
class insert {
import insert.*

private def batch(n: Int) = {
val widgets = Widget.generate(n)
insertBatch.updateMany(widgets)
}

private def values(n: Int) = {
val widgets = Widget.generate(n)
insertValues(widgets).run
}

private def array(n: Int) = {
val widgets = Widget.generate(n)
insertArrayValues(widgets).run
}

@Benchmark
@OperationsPerInvocation(512)
def batch_512(state: state): Int = state.transact(batch(512))

@Benchmark
@OperationsPerInvocation(1024)
def batch_1024(state: state): Int = state.transact(batch(1024))

@Benchmark
@OperationsPerInvocation(2048)
def batch_2048(state: state): Int = state.transact(batch(2048))

@Benchmark
@OperationsPerInvocation(512)
def values_512(state: state): Int = state.transact(values(512))

@Benchmark
@OperationsPerInvocation(1024)
def values_1024(state: state): Int = state.transact(values(1024))

@Benchmark
@OperationsPerInvocation(2048)
def values_2048(state: state): Int = state.transact(values(2048))

@Benchmark
@OperationsPerInvocation(512)
def array_512(state: state): Int = state.transact(array(512))

@Benchmark
@OperationsPerInvocation(1024)
def array_1024(state: state): Int = state.transact(array(1024))

@Benchmark
@OperationsPerInvocation(2048)
def array_2048(state: state): Int = state.transact(array(2048))
}
object insert {
import doobie.postgres.instances.array.*

final case class Widget(name: String, extensions: Int)
object Widget {
implicit val write: Write[Widget] = Write.derived

def generate(n: Int) = List.fill(n)(Widget("widget", n))
}

private val insertBatch = {
val sql = """
INSERT INTO bench_widget
(name, extensions)
VALUES
(?, ?)
"""
Update[Widget](sql)
}

private def insertValues(widgets: List[Widget]) = {
val sql = fr"""
INSERT INTO bench_widget
(name, extensions)
VALUES
${widgets.map(w => fr"(${w.name}, ${w.extensions})").intercalate(fr",")}
"""
sql.update
}

private def insertArrayValues(widgets: List[Widget]) = {
val n = widgets.length
val names = Array.ofDim[String](n)
val extensions = Array.ofDim[Int](n)
var i = 0
widgets.foreach { w =>
names(i) = w.name
extensions(i) = w.extensions
i = i + 1
}

val sql = fr"""
INSERT INTO bench_widget
(name, extensions)
SELECT * FROM unnest(
$names::text[],
$extensions::int4[]
)
"""
sql.update
}

private val ddl: ConnectionIO[Unit] =
sql"drop table if exists bench_widget".update.run *>
sql"""create table bench_widget (
name text not null,
extensions integer not null
)""".update.run.void

@State(Scope.Thread)
class state extends PostgresConnectionState {

@Setup()
override def setup(): Unit = {
super.setup()
transact(ddl)
}

@TearDown()
override def tearDown(): Unit = super.tearDown()
}
}