kschlesselmann
05/27/2021, 4:00 PM<http://logger.info|logger.info> { "Some expensive $string" }
. Now there's structured logging as well with https://github.com/logstash/logstash-logback-encoder. It would be great if I could use the lazy evaluated logging approach with multiple arguments as well.
I came up with something like
private fun KLogger.infoWithVarargSupplier(
vararg arguments: () -> Any,
message: () -> String,
) {
if (isInfoEnabled) {
info(
message.invoke(),
*arguments
.map { it.invoke() }
.toTypedArray(),
)
}
}
and now I'd like if there would be any huge performance impact in using a function like this. I tried creating a JMH benchmark but the results look suspicious … any suggestions?Big Chungus
05/27/2021, 4:58 PMOliver.O
05/27/2021, 4:59 PMkschlesselmann
05/28/2021, 6:01 AMkschlesselmann
05/28/2021, 6:03 AMkschlesselmann
05/28/2021, 6:03 AMpackage extensions
import net.logstash.logback.argument.StructuredArguments.kv
import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.Blackhole
import org.openjdk.jmh.runner.Runner
import org.openjdk.jmh.runner.options.Options
import org.openjdk.jmh.runner.options.OptionsBuilder
import java.util.concurrent.TimeUnit
fun testThing(
message: String,
vararg args: Any,
) = message.hashCode() + args.hashCode()
val enabled = true
fun testThingExtension(
vararg arguments: () -> Any,
message: () -> String,
) = if (enabled) {
testThing(
message.invoke(),
*arguments
.map { it.invoke() }
.toTypedArray()
)
} else {
0
}
fun testThingExtensionSingle(
arguments: () -> Array<Any>,
message: () -> String,
) = if (enabled) {
testThing(
message.invoke(),
*arguments.invoke(),
)
} else {
0
}
inline fun inlineTestThingExtension(
vararg arguments: () -> Any,
message: () -> String,
) = if (enabled) {
testThing(
message.invoke(),
*arguments
.map { it.invoke() }
.toTypedArray()
)
} else {
0
}
inline fun inlineTestThingExtensionSingle(
arguments: () -> Array<Any>,
message: () -> String,
) = if (enabled) {
testThing(
message.invoke(),
*arguments.invoke()
)
} else {
0
}
@BenchmarkMode(Mode.AverageTime) // Benchmark mode
@Warmup(iterations = 5) // Preheating times
@Measurement(iterations = 100, time = 5, timeUnit = TimeUnit.NANOSECONDS)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Threads(8) // number of test threads per process
@Fork(3)
open class SomeOtherBenchmark {
@Benchmark
fun standard(blackhole: Blackhole) {
val result = if (enabled) {
testThing("foo", kv("a", "b"), kv("c", "d"))
} else {
0
}
blackhole.consume(result)
}
@Benchmark
fun extension(blackhole: Blackhole) {
val result = testThingExtension(
{kv("a", "b")}, {kv("c", "d")}
) {
"foo"
}
blackhole.consume(result)
}
@Benchmark
fun inlineExtension(blackhole: Blackhole) {
val result = inlineTestThingExtension(
{kv("a", "b")}, {kv("c", "d")}
) {
"foo"
}
blackhole.consume(result)
}
@Benchmark
fun extensionSingle(blackhole: Blackhole) {
val result = testThingExtensionSingle(
{ arrayOf(kv("a", "b"), kv("c", "d")) }
) {
"foo"
}
blackhole.consume(result)
}
@Benchmark
fun inlineExtensionSingle(blackhole: Blackhole) {
val result = inlineTestThingExtensionSingle(
{ arrayOf(kv("a", "b"), kv("c", "d")) }
) {
"foo"
}
blackhole.consume(result)
}
companion object {
@JvmStatic
fun main(args: Array<String>) {
val options: Options = OptionsBuilder()
.include(".*" + SomeOtherBenchmark::class.java.simpleName + ".*")
.output("benchmark_test.log")
.build()
Runner(options).run()
}
}
}
kschlesselmann
05/28/2021, 6:04 AMkschlesselmann
05/28/2021, 6:04 AMkschlesselmann
05/28/2021, 6:05 AMBenchmark Mode Cnt Score Error Units
SomeOtherBenchmark.extension avgt 300 24089,809 ± 34911,345 ns/op
SomeOtherBenchmark.extensionSingle avgt 300 16363,556 ± 29068,461 ns/op
SomeOtherBenchmark.inlineExtension avgt 300 18925,285 ± 25892,763 ns/op
SomeOtherBenchmark.inlineExtensionSingle avgt 300 25488,181 ± 36613,759 ns/op
SomeOtherBenchmark.standard avgt 300 14638,310 ± 27306,244 ns/op
kschlesselmann
05/28/2021, 6:06 AMkschlesselmann
05/28/2021, 6:08 AMif (logger.isFooEnabled) {}
all the timeOliver.O
06/01/2021, 11:05 AMpackage test
import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.Blackhole
import org.openjdk.jmh.runner.Runner
import org.openjdk.jmh.runner.options.Options
import org.openjdk.jmh.runner.options.OptionsBuilder
import java.util.concurrent.TimeUnit
fun testThing(
message: Int,
vararg args: Int,
) = message * args.sum()
fun kv(k: Int, v: Int) = k * v
var enabled = true
var a = 1
var b = 2
var c = 3
var d = 4
var m = 10
fun testThingExtension(
vararg arguments: () -> Int,
message: () -> Int,
) = if (enabled) {
testThing(
message.invoke(),
*arguments
.map { it.invoke() }
.toIntArray()
)
} else {
0
}
fun testThingExtensionSingle(
arguments: () -> IntArray,
message: () -> Int,
) = if (enabled) {
testThing(
message.invoke(),
*arguments.invoke(),
)
} else {
0
}
inline fun inlineTestThingExtension(
vararg arguments: () -> Int,
message: () -> Int,
) = if (enabled) {
testThing(
message.invoke(),
*arguments
.map { it.invoke() }
.toIntArray()
)
} else {
0
}
inline fun inlineTestThingExtensionSingle(
arguments: () -> IntArray,
message: () -> Int,
) = if (enabled) {
testThing(
message.invoke(),
*arguments.invoke()
)
} else {
0
}
@Suppress("SpellCheckingInspection")
@BenchmarkMode(Mode.AverageTime) // Benchmark mode
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
//@Threads(8) // number of test threads per process
@Fork(1)
open class LoggingBenchmark {
@Benchmark
fun standard(blackhole: Blackhole) {
val result = if (enabled) {
testThing(m, kv(a, b), kv(c, d))
} else {
0
}
blackhole.consume(result)
}
@Benchmark
fun extension(blackhole: Blackhole) {
val result = testThingExtension(
{ kv(a, b) }, { kv(c, d) }
) {
m
}
blackhole.consume(result)
}
@Benchmark
fun inlineExtension(blackhole: Blackhole) {
val result = inlineTestThingExtension(
{ kv(a, b) }, { kv(c, d) }
) {
m
}
blackhole.consume(result)
}
@Benchmark
fun extensionSingle(blackhole: Blackhole) {
val result = testThingExtensionSingle(
{ intArrayOf(kv(a, b), kv(c, d)) }
) {
m
}
blackhole.consume(result)
}
@Benchmark
fun inlineExtensionSingle(blackhole: Blackhole) {
val result = inlineTestThingExtensionSingle(
{ intArrayOf(kv(a, b), kv(c, d)) }
) {
m
}
blackhole.consume(result)
}
companion object {
@JvmStatic
fun main(args: Array<String>) {
val options: Options = OptionsBuilder()
.include(".*" + LoggingBenchmark::class.java.simpleName + ".*")
.output("benchmark_test.log")
.build()
Runner(options).run()
}
}
}
Oliver.O
06/01/2021, 11:09 AMenabled = true
, 3 x 1 sec warmup, 3 x 5 seconds measurement:
Benchmark Mode Cnt Score Error Units
LoggingBenchmark.extension avgt 3 43.721 ± 1.742 ns/op
LoggingBenchmark.extensionSingle avgt 3 7.289 ± 1.792 ns/op
LoggingBenchmark.inlineExtension avgt 3 42.142 ± 1.353 ns/op
LoggingBenchmark.inlineExtensionSingle avgt 3 5.498 ± 0.105 ns/op
LoggingBenchmark.standard avgt 3 4.925 ± 0.021 ns/op
enabled = false
, 3 x 1 sec warmup, 3 x 5 seconds measurement:
Benchmark Mode Cnt Score Error Units
LoggingBenchmark.extension avgt 3 2.347 ± 0.183 ns/op
LoggingBenchmark.extensionSingle avgt 3 2.346 ± 0.081 ns/op
LoggingBenchmark.inlineExtension avgt 3 2.361 ± 0.304 ns/op
LoggingBenchmark.inlineExtensionSingle avgt 3 2.349 ± 0.021 ns/op
LoggingBenchmark.standard avgt 3 2.368 ± 0.477 ns/op
Oliver.O
06/01/2021, 11:10 AMkschlesselmann
06/01/2021, 11:15 AMkschlesselmann
06/01/2021, 2:18 PMOliver.O
06/01/2021, 2:31 PMkschlesselmann
06/01/2021, 2:37 PMkschlesselmann
06/01/2021, 3:30 PM