diff --git a/README.md b/README.md index f2a6488..7550367 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,9 @@ default value: `true`. If `true`, decodes all values to strings instead of typed #### decodeComponentsToNestedMaps default value: `true`. If `true`, decodes `components` to nested maps instead of unwrap component's map to message's main map. +## Performance +Component benchmark results available [here](docs/benchmarks/jmh-benchmark.md). + ## Release notes ### 0.1.0 + Dirty mode added. diff --git a/build.gradle b/build.gradle index bda24d6..57b3558 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id "application" - id "com.exactpro.th2.gradle.component" version "0.1.1" + id "com.exactpro.th2.gradle.component" version "0.1.2" id "org.jetbrains.kotlin.jvm" version "$kotlin_version" id "org.jetbrains.kotlin.kapt" version "$kotlin_version" id "me.champeau.jmh" version "0.7.2" @@ -95,11 +95,11 @@ dependencies { jmh "org.openjdk.jmh:jmh-generator-annprocess:$jmhVersion" jmh "org.openjdk.jmh:jmh-generator-bytecode:$jmhVersion" - testImplementation "org.junit.jupiter:junit-jupiter" + testImplementation "org.junit.jupiter:junit-jupiter:5.11.0" testImplementation "org.jetbrains.kotlin:kotlin-test-junit5" testImplementation "org.assertj:assertj-core:3.26.3" } test.useJUnitPlatform() application.mainClass = "com.exactpro.th2.codec.MainKt" -dependencyCheck.suppressionFile = file('suppressions.xml') \ No newline at end of file +dependencyCheck.suppressionFile = "suppressions.xml" \ No newline at end of file diff --git a/docs/benchmarks/jmh-benchmark.md b/docs/benchmarks/jmh-benchmark.md new file mode 100644 index 0000000..23d435e --- /dev/null +++ b/docs/benchmarks/jmh-benchmark.md @@ -0,0 +1,84 @@ +# JMH benchmark + +encode the following FIX message: + +```kotlin + ParsedMessage( + MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()), + EventId("test_id", "test_book", "test_scope", Instant.now()), + "ExecutionReport", + mutableMapOf("encode-mode" to "dirty"), + PROTOCOL, + mutableMapOf( + "header" to mutableMapOf( + "MsgSeqNum" to 10947, + "SenderCompID" to "SENDER", + "SendingTime" to LocalDateTime.parse("2023-04-19T10:36:07.415088"), + "TargetCompID" to "RECEIVER", + "BeginString" to "FIXT.1.1", + "BodyLength" to 295, + "MsgType" to "8" + ), + "ExecID" to "495504662", + "ClOrdID" to "zSuNbrBIZyVljs", + "OrigClOrdID" to "zSuNbrBIZyVljs", + "OrderID" to "49415882", + "ExecType" to '0', + "OrdStatus" to '0', + "LeavesQty" to BigDecimal(500), + "CumQty" to BigDecimal(500), + "SecurityID" to "NWDR", + "SecurityIDSource" to "8", + "TradingParty" to mutableMapOf( + "NoPartyIDs" to mutableListOf( + mutableMapOf( + "PartyID" to "NGALL1FX01", + "PartyIDSource" to 'D', + "PartyRole" to 76 + ), + mutableMapOf( + "PartyID" to "0", + "PartyIDSource" to 'P', + "PartyRole" to 3 + ) + ) + ), + "Account" to "test", + "OrdType" to 'A', + "TimeInForce" to '0', + "Side" to 'B', + "Symbol" to "ABC", + "OrderQty" to BigDecimal(500), + "Price" to BigDecimal(1000), + "Unknown" to "500", + "TransactTime" to LocalDateTime.parse("2018-02-05T10:38:08.000008"), + "trailer" to mutableMapOf( + "CheckSum" to "191" + ) + ) + ) +``` + +decode the same FIX message: + +8=FIXT.1.19=29535=849=SENDER56=RECEIVER34=1094752=20230419-10:36:07.41508817=49550466211=zSuNbrBIZyVljs41=zSuNbrBIZyVljs37=49415882150=039=0151=50014=50048=NWDR22=8453=2448=NGALL1FX01447=D452=76448=0447=P452=31=test40=A59=054=B55=ABC38=50044=100047=50060=20180205-10:38:08.00000810=191 + +Testing is carried out in two formats of parsed messages: String values and Typed values. + +## benchmark results for version 0.1.0-dev + +dirty mode: + +Benchmark Mode Cnt Score Error Units +FixNgCodecBenchmark.encodeFixMessageString thrpt 25 178479.225 ± 2851.079 ops/s +FixNgCodecBenchmark.encodeFixMessageTyped thrpt 25 263077.629 ± 3967.905 ops/s +FixNgCodecBenchmark.parseFixMessageString thrpt 25 173370.305 ± 1878.013 ops/s +FixNgCodecBenchmark.parseFixMessageTyped thrpt 25 186232.291 ± 1295.186 ops/s + +strict mode: + +Benchmark Mode Cnt Score Error Units +FixNgCodecBenchmark.encodeFixMessageString thrpt 25 179523.040 ± 3084.493 ops/s +FixNgCodecBenchmark.encodeFixMessageTyped thrpt 25 265769.868 ± 3893.223 ops/s +FixNgCodecBenchmark.parseFixMessageString thrpt 25 165978.593 ± 6474.860 ops/s +FixNgCodecBenchmark.parseFixMessageTyped thrpt 25 186475.155 ± 1548.224 ops/s \ No newline at end of file diff --git a/src/jmh/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecBenchmark.kt b/src/jmh/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecBenchmark.kt index f7c66f4..9cfb560 100644 --- a/src/jmh/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecBenchmark.kt +++ b/src/jmh/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecBenchmark.kt @@ -36,14 +36,18 @@ import org.openjdk.jmh.annotations.Mode import org.openjdk.jmh.annotations.Scope import org.openjdk.jmh.annotations.Setup import org.openjdk.jmh.annotations.State +import java.math.BigDecimal import java.time.Instant +import java.time.LocalDateTime @State(Scope.Benchmark) open class BenchmarkState { - lateinit var codec: IPipelineCodec - lateinit var rawBody: ByteBuf + lateinit var codecTyped: IPipelineCodec + lateinit var codecString: IPipelineCodec + private lateinit var rawBody: ByteBuf lateinit var rawGroup: MessageGroup - lateinit var parsedGroup: MessageGroup + lateinit var parsedGroupTyped: MessageGroup + lateinit var parsedGroupString: MessageGroup @Setup(Level.Trial) fun setup() { @@ -51,10 +55,12 @@ open class BenchmarkState { .getResourceAsStream("dictionary-benchmark.xml") .use(XmlDictionaryStructureLoader()::load) - codec = FixNgCodec(dictionary, FixNgCodecSettings(dictionary = "", decodeValuesToStrings = true)) + codecTyped = FixNgCodec(dictionary, FixNgCodecSettings(dictionary = "", decodeValuesToStrings = false)) + codecString = FixNgCodec(dictionary, FixNgCodecSettings(dictionary = "", decodeValuesToStrings = true)) rawBody = Unpooled.wrappedBuffer(MSG_CORRECT.toByteArray(Charsets.US_ASCII)) - rawGroup = MessageGroup(listOf(RawMessage(id = parsedMessage.id, eventId = parsedMessage.eventId, body = rawBody))) - parsedGroup = MessageGroup(listOf(parsedMessage)) + rawGroup = MessageGroup(listOf(RawMessage(id = parsedMessageTyped.id, eventId = parsedMessageTyped.eventId, body = rawBody, metadata = mutableMapOf("encode-mode" to "dirty")))) + parsedGroupTyped = MessageGroup(listOf(parsedMessageTyped)) + parsedGroupString = MessageGroup(listOf(parsedMessageString)) } @Setup(Level.Invocation) @@ -64,20 +70,21 @@ open class BenchmarkState { companion object { private const val MSG_CORRECT = "8=FIXT.1.1\u00019=295\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=191\u0001" - private val parsedMessage = ParsedMessage( + + private val parsedMessageTyped = ParsedMessage( MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()), EventId("test_id", "test_book", "test_scope", Instant.now()), "ExecutionReport", - mapOf("encode-mode" to "dirty"), + mutableMapOf("encode-mode" to "dirty"), PROTOCOL, - mapOf( - "header" to mapOf( - "MsgSeqNum" to "10947", + mutableMapOf( + "header" to mutableMapOf( + "MsgSeqNum" to 10947, "SenderCompID" to "SENDER", - "SendingTime" to "2023-04-19T10:36:07.415088", + "SendingTime" to LocalDateTime.parse("2023-04-19T10:36:07.415088"), "TargetCompID" to "RECEIVER", "BeginString" to "FIXT.1.1", - "BodyLength" to "295", + "BodyLength" to 295, "MsgType" to "8" ), "ExecID" to "495504662", @@ -86,52 +93,80 @@ open class BenchmarkState { "OrderID" to "49415882", "ExecType" to '0', "OrdStatus" to '0', - "LeavesQty" to "500", - "CumQty" to "500", + "LeavesQty" to BigDecimal(500), + "CumQty" to BigDecimal(500), "SecurityID" to "NWDR", "SecurityIDSource" to "8", - "TradingParty" to mapOf( - "NoPartyIDs" to listOf( - mapOf( + "TradingParty" to mutableMapOf( + "NoPartyIDs" to mutableListOf( + mutableMapOf( "PartyID" to "NGALL1FX01", - "PartyIDSource" to "D", - "PartyRole" to "76" + "PartyIDSource" to 'D', + "PartyRole" to 76 ), - mapOf( + mutableMapOf( "PartyID" to "0", - "PartyIDSource" to "P", - "PartyRole" to "3" + "PartyIDSource" to 'P', + "PartyRole" to 3 ) ) ), "Account" to "test", - "OrdType" to "A", - "TimeInForce" to "0", - "Side" to "B", + "OrdType" to 'A', + "TimeInForce" to '0', + "Side" to 'B', "Symbol" to "ABC", - "OrderQty" to "500", - "Price" to "1000", + "OrderQty" to BigDecimal(500), + "Price" to BigDecimal(1000), "Unknown" to "500", - "TransactTime" to "2018-02-05T10:38:08.000008", - "trailer" to mapOf( + "TransactTime" to LocalDateTime.parse("2018-02-05T10:38:08.000008"), + "trailer" to mutableMapOf( "CheckSum" to "191" ) ) ) + + @Suppress("UNCHECKED_CAST") + private val parsedMessageString = ParsedMessage( + MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()), + EventId("test_id", "test_book", "test_scope", Instant.now()), + "ExecutionReport", + mutableMapOf(/*"encode-mode" to "dirty"*/), + PROTOCOL, + convertValuesToString(parsedMessageTyped.body) as MutableMap + ) + + private fun convertValuesToString(value: Any?): Any = when (value) { + is Map<*, *> -> value.mapValues { convertValuesToString(it.value) } + is List<*> -> value.map(::convertValuesToString) + else -> value.toString() + } } } open class FixNgCodecBenchmark { @Benchmark @BenchmarkMode(Mode.Throughput) - fun encodeFixMessage(state: BenchmarkState) { - state.codec.encode(state.parsedGroup, context) + fun encodeFixMessageTyped(state: BenchmarkState) { + state.codecTyped.encode(state.parsedGroupTyped, context) + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + fun parseFixMessageTyped(state: BenchmarkState) { + state.codecTyped.decode(state.rawGroup, context) + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + fun encodeFixMessageString(state: BenchmarkState) { + state.codecString.encode(state.parsedGroupString, context) } @Benchmark @BenchmarkMode(Mode.Throughput) - fun parseFixMessage(state: BenchmarkState) { - state.codec.decode(state.rawGroup, context) + fun parseFixMessageString(state: BenchmarkState) { + state.codecString.decode(state.rawGroup, context) } companion object {