diff --git a/ExternalBenchmarks/Benchmarks/ExternalBenchmarks/ExternalBenchmarks.swift b/ExternalBenchmarks/Benchmarks/ExternalBenchmarks/ExternalBenchmarks.swift index 7f4ba78..2333a30 100644 --- a/ExternalBenchmarks/Benchmarks/ExternalBenchmarks/ExternalBenchmarks.swift +++ b/ExternalBenchmarks/Benchmarks/ExternalBenchmarks/ExternalBenchmarks.swift @@ -6,42 +6,6 @@ import ZippyJSON @main extension BenchmarkRunner {} // Required for the main() definition to no get linker errors -/* - - Interesting comparison benchmark for parsing the JSON, turns out that hand-parsing is a - LOT faster than even ExtrasJSON Decodable conformance implementations. ExtrasJSON is still - notably faster than Foundation, but the hand-parse optimization was surprising to me. - - swift package benchmark --grouping metric - - ExternalBenchmarks - ============================================================================================================================ - - Throughput (scaled / s) - ╒════════════════════════════════════════╤═════╤═════╤══════╤═════╤═════╤═════╤══════╤═════════╕ - │ Test │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │ - ╞════════════════════════════════════════╪═════╪═════╪══════╪═════╪═════╪═════╪══════╪═════════╡ - │ Custom parse JSON into trace │ 82 │ 79 │ 78 │ 76 │ 75 │ 73 │ 68 │ 300 │ - ├────────────────────────────────────────┼─────┼─────┼──────┼─────┼─────┼─────┼──────┼─────────┤ - │ ExtrasJSON decode JSON into trace │ 6 │ 6 │ 6 │ 6 │ 6 │ 5 │ 5 │ 29 │ - ├────────────────────────────────────────┼─────┼─────┼──────┼─────┼─────┼─────┼──────┼─────────┤ - │ Foundation decode JSON into trace │ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │ 7 │ - ├────────────────────────────────────────┼─────┼─────┼──────┼─────┼─────┼─────┼──────┼─────────┤ - │ ZippyJSON decode JSON into trace │ 8 │ 8 │ 8 │ 7 │ 7 │ 7 │ 7 │ 38 │ - ╘════════════════════════════════════════╧═════╧═════╧══════╧═════╧═════╧═════╧══════╧═════════ - Time (wall clock) - ╒════════════════════════════════════════╤═════╤═════╤══════╤═════╤═════╤═════╤══════╤═════════╕ - │ Test │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │ - ╞════════════════════════════════════════╪═════╪═════╪══════╪═════╪═════╪═════╪══════╪═════════╡ - │ Custom parse JSON into trace (ms) │ 12 │ 13 │ 13 │ 13 │ 13 │ 14 │ 15 │ 300 │ - ├────────────────────────────────────────┼─────┼─────┼──────┼─────┼─────┼─────┼──────┼─────────┤ - │ ExtrasJSON decode JSON into trace (ms) │ 176 │ 176 │ 177 │ 178 │ 180 │ 184 │ 184 │ 29 │ - ├────────────────────────────────────────┼─────┼─────┼──────┼─────┼─────┼─────┼──────┼─────────┤ - │ Foundation decode JSON into trace (ms) │ 823 │ 823 │ 825 │ 825 │ 825 │ 825 │ 825 │ 7 │ - ├────────────────────────────────────────┼─────┼─────┼──────┼─────┼─────┼─────┼──────┼─────────┤ - │ ZippyJSON decode JSON into trace (ms) │ 132 │ 132 │ 133 │ 134 │ 134 │ 136 │ 136 │ 38 │ - ╘════════════════════════════════════════╧═════╧═════╧══════╧═════╧═════╧═════╧══════╧═════════╛ - */ enum TextOp { case insert(cursor: UInt32, value: String) case delete(cursor: UInt32, count: UInt32) @@ -160,84 +124,114 @@ func parseJSONIntoTrace(topOfTrace: JSONValue) async -> Trace { @_dynamicReplacement(for: registerBenchmarks) // And this is how we register our benchmarks func benchmarks() { - Benchmark.defaultConfiguration.maxIterations = .count(300) - Benchmark.defaultConfiguration.maxDuration = .seconds(5) - - Benchmark("Loading JSON trace data", - configuration: .init(metrics: [.throughput, .wallClock])) - { benchmark in - for _ in benchmark.scaledIterations { - await blackHole(loadEditingTrace()) - } - } - - Benchmark("parse JSON with Swift Extras parser", - configuration: .init(metrics: [.throughput, .wallClock])) - { benchmark in - for _ in benchmark.scaledIterations { - let data = await loadEditingTrace() - benchmark.startMeasurement() - await blackHole(parseDataIntoJSON(data: data)) - benchmark.stopMeasurement() - } - } - - Benchmark("Custom parse JSON into trace", - configuration: .init(metrics: [.throughput, .wallClock])) - { benchmark in - for _ in benchmark.scaledIterations { - let data = await loadEditingTrace() - let jsonValue = await parseDataIntoJSON(data: data) - benchmark.startMeasurement() - await blackHole(parseJSONIntoTrace(topOfTrace: jsonValue)) - benchmark.stopMeasurement() - } - } - - Benchmark("Foundation decode JSON into trace", - configuration: .init(metrics: [.throughput, .wallClock])) - { benchmark in - for _ in benchmark.scaledIterations { - let data = await loadEditingTrace() - benchmark.startMeasurement() - await blackHole(decodeIntoTrace(data: data)) - benchmark.stopMeasurement() - } - } - - Benchmark("ExtrasJSON decode JSON into trace", - configuration: .init(metrics: [.throughput, .wallClock])) - { benchmark in - for _ in benchmark.scaledIterations { - let data = await loadEditingTrace() - benchmark.startMeasurement() - await blackHole(decodeXIntoTrace(data: data)) - benchmark.stopMeasurement() - } - } - - Benchmark("ZippyJSON decode JSON into trace", - configuration: .init(metrics: [.throughput, .wallClock])) - { benchmark in - for _ in benchmark.scaledIterations { - let data = await loadEditingTrace() - benchmark.startMeasurement() - await blackHole(decodeZIntoTrace(data: data)) - benchmark.stopMeasurement() - } - } +// Benchmark("Loading JSON trace data", +// configuration: .init(metrics: [.throughput, .wallClock], desiredIterations: 20)) { benchmark in +// for _ in benchmark.scaledIterations { +// blackHole(await loadEditingTrace()) +// } +// } +// +// Benchmark("parse JSON with Swift Extras parser", +// configuration: .init(metrics: [.throughput, .wallClock], desiredIterations: 50)) { benchmark in +// for _ in benchmark.scaledIterations { +// let data = await loadEditingTrace() +// benchmark.startMeasurement() +// blackHole(await parseDataIntoJSON(data: data)) +// benchmark.stopMeasurement() +// } +// } + +// Benchmark("Custom parse JSON into trace", +// configuration: .init(metrics: [.throughput, .wallClock])) +// { benchmark in +// for _ in benchmark.scaledIterations { +// let data = await loadEditingTrace() +// let jsonValue = await parseDataIntoJSON(data: data) +// benchmark.startMeasurement() +// await blackHole(parseJSONIntoTrace(topOfTrace: jsonValue)) +// benchmark.stopMeasurement() +// } +// } +// +// Benchmark("Foundation decode JSON into trace", +// configuration: .init(metrics: [.throughput, .wallClock])) +// { benchmark in +// for _ in benchmark.scaledIterations { +// let data = await loadEditingTrace() +// benchmark.startMeasurement() +// await blackHole(decodeIntoTrace(data: data)) +// benchmark.stopMeasurement() +// } +// } +// +// Benchmark("ExtrasJSON decode JSON into trace", +// configuration: .init(metrics: [.throughput, .wallClock])) +// { benchmark in +// for _ in benchmark.scaledIterations { +// let data = await loadEditingTrace() +// benchmark.startMeasurement() +// await blackHole(decodeXIntoTrace(data: data)) +// benchmark.stopMeasurement() +// } +// } +// +// Benchmark("ZippyJSON decode JSON into trace", +// configuration: .init(metrics: [.throughput, .wallClock])) +// { benchmark in +// for _ in benchmark.scaledIterations { +// let data = await loadEditingTrace() +// benchmark.startMeasurement() +// await blackHole(decodeZIntoTrace(data: data)) +// benchmark.stopMeasurement() +// } +// } + + /* + Interesting comparison benchmark for parsing the JSON, turns out that hand-parsing is a + LOT faster than even ExtrasJSON Decodable conformance implementations. ExtrasJSON is still + notably faster than Foundation, but the hand-parse optimization was surprising to me. + + swift package benchmark --grouping metric + + ExternalBenchmarks + ============================================================================================================================ + + Throughput (scaled / s) + ╒════════════════════════════════════════╤═════╤═════╤══════╤═════╤═════╤═════╤══════╤═════════╕ + │ Test │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │ + ╞════════════════════════════════════════╪═════╪═════╪══════╪═════╪═════╪═════╪══════╪═════════╡ + │ Custom parse JSON into trace │ 82 │ 79 │ 78 │ 76 │ 75 │ 73 │ 68 │ 300 │ + ├────────────────────────────────────────┼─────┼─────┼──────┼─────┼─────┼─────┼──────┼─────────┤ + │ ExtrasJSON decode JSON into trace │ 6 │ 6 │ 6 │ 6 │ 6 │ 5 │ 5 │ 29 │ + ├────────────────────────────────────────┼─────┼─────┼──────┼─────┼─────┼─────┼──────┼─────────┤ + │ Foundation decode JSON into trace │ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │ 7 │ + ├────────────────────────────────────────┼─────┼─────┼──────┼─────┼─────┼─────┼──────┼─────────┤ + │ ZippyJSON decode JSON into trace │ 8 │ 8 │ 8 │ 7 │ 7 │ 7 │ 7 │ 38 │ + ╘════════════════════════════════════════╧═════╧═════╧══════╧═════╧═════╧═════╧══════╧═════════ + Time (wall clock) + ╒════════════════════════════════════════╤═════╤═════╤══════╤═════╤═════╤═════╤══════╤═════════╕ + │ Test │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │ + ╞════════════════════════════════════════╪═════╪═════╪══════╪═════╪═════╪═════╪══════╪═════════╡ + │ Custom parse JSON into trace (ms) │ 12 │ 13 │ 13 │ 13 │ 13 │ 14 │ 15 │ 300 │ + ├────────────────────────────────────────┼─────┼─────┼──────┼─────┼─────┼─────┼──────┼─────────┤ + │ ExtrasJSON decode JSON into trace (ms) │ 176 │ 176 │ 177 │ 178 │ 180 │ 184 │ 184 │ 29 │ + ├────────────────────────────────────────┼─────┼─────┼──────┼─────┼─────┼─────┼──────┼─────────┤ + │ Foundation decode JSON into trace (ms) │ 823 │ 823 │ 825 │ 825 │ 825 │ 825 │ 825 │ 7 │ + ├────────────────────────────────────────┼─────┼─────┼──────┼─────┼─────┼─────┼──────┼─────────┤ + │ ZippyJSON decode JSON into trace (ms) │ 132 │ 132 │ 133 │ 134 │ 134 │ 136 │ 136 │ 38 │ + ╘════════════════════════════════════════╧═════╧═════╧══════╧═════╧═════╧═════╧══════╧═════════╛ + */ + Benchmark("Create single-character List CRDT", - configuration: .init(metrics: BenchmarkMetric.all, scalingFactor: .kilo)) - { benchmark in + configuration: .init(timeUnits: .microseconds, scalingFactor: .kilo)) { benchmark in for _ in benchmark.scaledIterations { blackHole(blackHole(List(actorId: "a", ["a"]))) } } Benchmark("List six-character append", - configuration: .init(metrics: [.throughput, .wallClock], scalingFactor: .kilo)) - { benchmark in + configuration: .init(timeUnits: .microseconds, scalingFactor: .kilo)) { benchmark in for _ in benchmark.scaledIterations { var mylist = List(actorId: "a", ["a"]) benchmark.startMeasurement() @@ -247,8 +241,7 @@ func benchmarks() { } Benchmark("List six-character append, individual characters", - configuration: .init(metrics: [.throughput, .wallClock], scalingFactor: .kilo)) - { benchmark in + configuration: .init(timeUnits: .microseconds, scalingFactor: .kilo)) { benchmark in for _ in benchmark.scaledIterations { var mylist = List(actorId: "a", ["a"]) let appendList = [" ", "h", "e", "l", "l", "o"] diff --git a/ExternalBenchmarks/Benchmarks/ExternalBenchmarks/README.md b/ExternalBenchmarks/Benchmarks/ExternalBenchmarks/README.md index 9a1edc6..753ede8 100644 --- a/ExternalBenchmarks/Benchmarks/ExternalBenchmarks/README.md +++ b/ExternalBenchmarks/Benchmarks/ExternalBenchmarks/README.md @@ -8,4 +8,3 @@ General format of the JSON: ref article for decoding in an enum w/ associated value in swift: https://www.donnywals.com/splitting-a-json-object-into-an-enum-and-an-associated-object-with-codable/ - diff --git a/ExternalBenchmarks/README.md b/ExternalBenchmarks/README.md index e0f5544..ce8ca9d 100644 --- a/ExternalBenchmarks/README.md +++ b/ExternalBenchmarks/README.md @@ -6,6 +6,86 @@ This code is explicitly in a subdirectory with a local reference to CRDT in orde to preserve the backwards compatibility of the CRDT package itself, where this benchmark library requires macOS 13 (or Linux). +There are multiple sets of benchmarks included, exploring a few different avenues - speed of JSON loading, +CRDT list creation, etc. Uncomment the ones you're interested in exploring and run 'em. + To run the benchmarks, invoke: - swift package benchmark + swift package benchmark --grouping metric --format markdown + +## Baseline `9ad6336d2bbc` + +``` +Host 'MacBookPro' with 8 'arm64' processors with 16 GB memory, running: +Darwin Kernel Version 24.0.0: Tue Sep 24 23:36:26 PDT 2024; root:xnu-11215.1.12~1/RELEASE_ARM64_T8103 +``` +### Malloc (total) + +``` +╒════════════════════════════════════════════════════════════════════════╤══════════╤══════════╤══════════╤══════════╤══════════╤══════════╤══════════╤══════════╕ +│ Test │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │ +╞════════════════════════════════════════════════════════════════════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╡ +│ ExternalBenchmarks:Create single-character List CRDT │ 4000 │ 4000 │ 4000 │ 4000 │ 4000 │ 4000 │ 4000 │ 998 │ +├────────────────────────────────────────────────────────────────────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ +│ ExternalBenchmarks:List six-character append │ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │ 35000 │ +├────────────────────────────────────────────────────────────────────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ +│ ExternalBenchmarks:List six-character append, individual characters │ 9 │ 9 │ 9 │ 9 │ 9 │ 9 │ 9 │ 30000 │ +╘════════════════════════════════════════════════════════════════════════╧══════════╧══════════╧══════════╧══════════╧══════════╧══════════╧══════════╧══════════╛ + +``` +### Memory (resident peak) + +``` +╒════════════════════════════════════════════════════════════════════════╤══════════╤══════════╤══════════╤══════════╤══════════╤══════════╤══════════╤══════════╕ +│ Test │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │ +╞════════════════════════════════════════════════════════════════════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╡ +│ ExternalBenchmarks:Create single-character List CRDT (K) │ 7716 │ 7753 │ 7766 │ 7766 │ 7766 │ 7766 │ 7766 │ 998 │ +├────────────────────────────────────────────────────────────────────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ +│ ExternalBenchmarks:List six-character append (K) │ 7733 │ 7770 │ 7770 │ 7782 │ 7782 │ 7782 │ 7782 │ 35000 │ +├────────────────────────────────────────────────────────────────────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ +│ ExternalBenchmarks:List six-character append, individual characters (… │ 7716 │ 7753 │ 7766 │ 7766 │ 7766 │ 7766 │ 7766 │ 30000 │ +╘════════════════════════════════════════════════════════════════════════╧══════════╧══════════╧══════════╧══════════╧══════════╧══════════╧══════════╧══════════╛ + +``` +### Throughput (# / s) + +``` +╒════════════════════════════════════════════════════════════════════════╤══════════╤══════════╤══════════╤══════════╤══════════╤══════════╤══════════╤══════════╕ +│ Test │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │ +╞════════════════════════════════════════════════════════════════════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╡ +│ ExternalBenchmarks:Create single-character List CRDT │ 1059 │ 1041 │ 1022 │ 1018 │ 1003 │ 929 │ 887 │ 998 │ +├────────────────────────────────────────────────────────────────────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ +│ ExternalBenchmarks:List six-character append (K) │ 521 │ 489 │ 489 │ 489 │ 480 │ 461 │ 37 │ 35000 │ +├────────────────────────────────────────────────────────────────────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ +│ ExternalBenchmarks:List six-character append, individual characters (… │ 193 │ 163 │ 162 │ 162 │ 161 │ 156 │ 31 │ 30000 │ +╘════════════════════════════════════════════════════════════════════════╧══════════╧══════════╧══════════╧══════════╧══════════╧══════════╧══════════╧══════════╛ + +``` +### Time (total CPU) + +``` +╒════════════════════════════════════════════════════════════════════════╤══════════╤══════════╤══════════╤══════════╤══════════╤══════════╤══════════╤══════════╕ +│ Test │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │ +╞════════════════════════════════════════════════════════════════════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╡ +│ ExternalBenchmarks:Create single-character List CRDT (μs) │ 944 │ 961 │ 978 │ 983 │ 997 │ 1073 │ 1101 │ 998 │ +├────────────────────────────────────────────────────────────────────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ +│ ExternalBenchmarks:List six-character append (μs) │ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │ 26 │ 35000 │ +├────────────────────────────────────────────────────────────────────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ +│ ExternalBenchmarks:List six-character append, individual characters (… │ 5 │ 6 │ 6 │ 6 │ 6 │ 6 │ 32 │ 30000 │ +╘════════════════════════════════════════════════════════════════════════╧══════════╧══════════╧══════════╧══════════╧══════════╧══════════╧══════════╧══════════╛ + +``` +### Time (wall clock) + +``` +╒════════════════════════════════════════════════════════════════════════╤══════════╤══════════╤══════════╤══════════╤══════════╤══════════╤══════════╤══════════╕ +│ Test │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │ +╞════════════════════════════════════════════════════════════════════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╡ +│ ExternalBenchmarks:Create single-character List CRDT (μs) │ 943 │ 960 │ 978 │ 982 │ 997 │ 1077 │ 1127 │ 998 │ +├────────────────────────────────────────────────────────────────────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ +│ ExternalBenchmarks:List six-character append (μs) │ 1 │ 2 │ 2 │ 2 │ 2 │ 2 │ 26 │ 35000 │ +├────────────────────────────────────────────────────────────────────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ +│ ExternalBenchmarks:List six-character append, individual characters (… │ 5 │ 6 │ 6 │ 6 │ 6 │ 6 │ 31 │ 30000 │ +╘════════════════════════════════════════════════════════════════════════╧══════════╧══════════╧══════════╧══════════╧══════════╧══════════╧══════════╧══════════╛ + +```