diff --git a/pkgs/dart_model/benchmark/builder_maps_builder_wire_benchmark.dart b/pkgs/dart_model/benchmark/builder_maps_builder_wire_benchmark.dart index 6b0fba0..db126de 100644 --- a/pkgs/dart_model/benchmark/builder_maps_builder_wire_benchmark.dart +++ b/pkgs/dart_model/benchmark/builder_maps_builder_wire_benchmark.dart @@ -2,6 +2,8 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:typed_data'; + import 'package:dart_model/src/json_buffer/json_buffer_builder.dart'; import 'serialization_benchmark.dart'; @@ -11,12 +13,12 @@ JsonBufferBuilder? runningBuffer; /// Benchmark accumulating data directly into a [JsonBufferBuilder]. class BuilderMapsBuilderWireBenchmark extends SerializationBenchmark { @override - void run() { - createData(); - - serialized = runningBuffer!.serialize(); + Uint8List serialize(Map data) { + assert(data == runningBuffer!.map); + return runningBuffer!.serialize(); } + @override Map createData() { final buffer = runningBuffer = JsonBufferBuilder(); final map = buffer.map; @@ -44,9 +46,8 @@ class BuilderMapsBuilderWireBenchmark extends SerializationBenchmark { } @override - void deserialize() { - deserialized = JsonBufferBuilder.deserialize(serialized!).map; - } + Map deserialize(Uint8List serialized) => + JsonBufferBuilder.deserialize(serialized).map; Member _makeMember(String key) { final intKey = key.length; diff --git a/pkgs/dart_model/benchmark/builder_maps_json_wire_benchmark.dart b/pkgs/dart_model/benchmark/builder_maps_json_wire_benchmark.dart index 4d48c1a..7e78e12 100644 --- a/pkgs/dart_model/benchmark/builder_maps_json_wire_benchmark.dart +++ b/pkgs/dart_model/benchmark/builder_maps_json_wire_benchmark.dart @@ -11,12 +11,10 @@ import 'builder_maps_builder_wire_benchmark.dart'; /// serializing it to JSON. class BuilderMapsJsonWireBenchmark extends BuilderMapsBuilderWireBenchmark { @override - void run() { - serialized = json.fuse(utf8).encode(createData()) as Uint8List; - } + Uint8List serialize(Map data) => + json.fuse(utf8).encode(data) as Uint8List; @override - void deserialize() { - deserialized = json.fuse(utf8).decode(serialized!) as Map; - } + Map deserialize(Uint8List serialized) => + json.fuse(utf8).decode(serialized) as Map; } diff --git a/pkgs/dart_model/benchmark/lazy_maps_buffer_wire_benchmark.dart b/pkgs/dart_model/benchmark/lazy_maps_buffer_wire_benchmark.dart index 34259b5..a800e3f 100644 --- a/pkgs/dart_model/benchmark/lazy_maps_buffer_wire_benchmark.dart +++ b/pkgs/dart_model/benchmark/lazy_maps_buffer_wire_benchmark.dart @@ -2,16 +2,18 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:typed_data'; + import 'json_buffer.dart'; import 'serialization_benchmark.dart'; /// Benchmark accumulating data into a [JsonBuffer] via [LazyMap]. class LazyMapsBufferWireBenchmark extends SerializationBenchmark { @override - void run() { - serialized = JsonBuffer(createData()).serialize(); - } + Uint8List serialize(Map data) => + JsonBuffer(data).serialize(); + @override LazyMap createData() { return LazyMap(mapKeys, (key) { final intKey = int.parse(key); @@ -44,9 +46,8 @@ class LazyMapsBufferWireBenchmark extends SerializationBenchmark { } @override - void deserialize() { - deserialized = JsonBuffer.deserialize(serialized!).asMap; - } + Map deserialize(Uint8List serialized) => + JsonBuffer.deserialize(serialized).asMap; } /// An interface. diff --git a/pkgs/dart_model/benchmark/lazy_maps_json_wire_benchmark.dart b/pkgs/dart_model/benchmark/lazy_maps_json_wire_benchmark.dart index 632aba8..4f4ec05 100644 --- a/pkgs/dart_model/benchmark/lazy_maps_json_wire_benchmark.dart +++ b/pkgs/dart_model/benchmark/lazy_maps_json_wire_benchmark.dart @@ -11,12 +11,10 @@ import 'lazy_maps_buffer_wire_benchmark.dart'; /// serializing it to JSON. class LazyMapsJsonWireBenchmark extends LazyMapsBufferWireBenchmark { @override - void run() { - serialized = json.fuse(utf8).encode(createData()) as Uint8List; - } + Uint8List serialize(Map data) => + json.fuse(utf8).encode(data) as Uint8List; @override - void deserialize() { - deserialized = json.fuse(utf8).decode(serialized!) as Map; - } + Map deserialize(Uint8List serialized) => + json.fuse(utf8).decode(serialized) as Map; } diff --git a/pkgs/dart_model/benchmark/lazy_wrappers_buffer_wire_benchmark.dart b/pkgs/dart_model/benchmark/lazy_wrappers_buffer_wire_benchmark.dart index d92a3e6..f34c578 100644 --- a/pkgs/dart_model/benchmark/lazy_wrappers_buffer_wire_benchmark.dart +++ b/pkgs/dart_model/benchmark/lazy_wrappers_buffer_wire_benchmark.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:collection'; +import 'dart:typed_data'; import 'package:dart_model/src/json_buffer/json_buffer_builder.dart'; @@ -15,12 +16,12 @@ JsonBufferBuilder? runningBuffer; /// extension type). class LazyWrappersBufferWireBenchmark extends SerializationBenchmark { @override - void run() { - createData(); - - serialized = runningBuffer!.serialize(); + Uint8List serialize(Map data) { + assert(data == runningBuffer!.map); + return runningBuffer!.serialize(); } + @override Map createData() { final buffer = runningBuffer = JsonBufferBuilder(); final map = buffer.map; @@ -62,13 +63,12 @@ class LazyWrappersBufferWireBenchmark extends SerializationBenchmark { } @override - void deserialize() { - deserialized = _LazyMap( - JsonBufferBuilder.deserialize(serialized!).map, - (json) => Interface.fromJson(json as Map), - (i) => i.toJson(), - ); - } + Map deserialize(Uint8List serialized) => + _LazyMap( + JsonBufferBuilder.deserialize(serialized).map, + (json) => Interface.fromJson(json as Map), + (i) => i.toJson(), + ); } class _LazyMap extends MapBase { diff --git a/pkgs/dart_model/benchmark/main.dart b/pkgs/dart_model/benchmark/main.dart index c7d0615..7818da4 100644 --- a/pkgs/dart_model/benchmark/main.dart +++ b/pkgs/dart_model/benchmark/main.dart @@ -2,6 +2,8 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:math'; + import 'package:collection/collection.dart'; import 'builder_maps_builder_wire_benchmark.dart'; @@ -15,51 +17,76 @@ import 'regular_dart_classes.dart' as regular; import 'sdk_maps_buffer_wire_benchmark.dart'; import 'sdk_maps_builder_wire_benchmark.dart'; import 'sdk_maps_json_wire_benchmark.dart'; +import 'serialization_benchmark.dart'; void main() { - final sdkMapsJsonWireBenchmark = SdkMapsJsonWireBenchmark(); - final lazyMapsBufferWireBenchmark = LazyMapsBufferWireBenchmark(); - final lazyWrappersBufferWireBenchmark = LazyWrappersBufferWireBenchmark(); - final builderMapsBuilderWireBenchmark = BuilderMapsBuilderWireBenchmark(); - final regularClassesBufferWireBenchmark = RegularClassesBufferWireBenchmark(); final serializationBenchmarks = [ - sdkMapsJsonWireBenchmark, + SdkMapsJsonWireBenchmark(), SdkMapsBufferWireBenchmark(), SdkMapsBuilderWireBenchmark(), LazyMapsJsonWireBenchmark(), - lazyMapsBufferWireBenchmark, - lazyWrappersBufferWireBenchmark, + LazyMapsBufferWireBenchmark(), + LazyWrappersBufferWireBenchmark(), BuilderMapsJsonWireBenchmark(), - builderMapsBuilderWireBenchmark, - regularClassesBufferWireBenchmark, + BuilderMapsBuilderWireBenchmark(), + RegularClassesBufferWireBenchmark(), ]; for (var i = 0; i != 3; ++i) { - for (final benchmark in serializationBenchmarks) { - final measure = benchmark.measure().toMilliseconds; - final paddedName = benchmark.name.padLeft(31); - final paddedMeasure = '${measure}ms'.padLeft(6); + // Collects the total measurements from all phases, per benchmark. + final totals = { + for (var benchmark in serializationBenchmarks) benchmark: 0, + }; + + for (var stage in BenchmarkStage.values) { + var padding = 0; + for (final benchmark in serializationBenchmarks) { + benchmark.stage = stage; + padding = max(padding, benchmark.name.length); + } - final paddedBytes = '${benchmark.serialized!.length}'.padLeft(7); - print('$paddedName: $paddedMeasure, $paddedBytes bytes'); - benchmark.deserialize(); + for (final benchmark in serializationBenchmarks) { + final measure = benchmark.measure().toMilliseconds; + totals[benchmark] = totals[benchmark]! + measure; + + var buffer = + StringBuffer(benchmark.name.padLeft(padding + 1)) + ..write(': ') + ..write('${measure}ms'.padLeft(6)); + + switch (stage) { + case BenchmarkStage.serialize: + final paddedBytes = '${benchmark.bytes}'.padLeft(7); + buffer.write(', $paddedBytes bytes'); + case BenchmarkStage.process: + buffer.write(', hash ${benchmark.hashResult}'); + default: + } + print(buffer.toString()); + } } - print(''); - for (final benchmark in [ - sdkMapsJsonWireBenchmark.processBenchmark(), - lazyMapsBufferWireBenchmark.processBenchmark(), - lazyWrappersBufferWireBenchmark.processBenchmark(), - builderMapsBuilderWireBenchmark.processBenchmark(), - regularClassesBufferWireBenchmark.processBenchmark(), - ]) { - final measure = benchmark.measure().toMilliseconds; - final paddedName = benchmark.name.padLeft(36); - final paddedMeasure = '${measure}ms'.padLeft(6); + // Add up the totals and print them. + { + var padding = 0; + String name(SerializationBenchmark benchmark) => + '${benchmark.runtimeType}-total'; - print('$paddedName: $paddedMeasure, hash ${benchmark.computedResult}'); + for (final benchmark in serializationBenchmarks) { + padding = max(padding, name(benchmark).length); + } + for (var benchmark in serializationBenchmarks) { + var buffer = + StringBuffer(name(benchmark).padLeft(padding + 1)) + ..write(':') + ..write('${totals[benchmark]}ms'.padLeft(7)); + print(buffer.toString()); + } } + print(''); + + print('validating benchmark results (this is slow)'); for (final benchmark in serializationBenchmarks.skip(1)) { var deserialized = benchmark.deserialized; // Need to unwrap these to compare them as raw maps. diff --git a/pkgs/dart_model/benchmark/regular_dart_classes.dart b/pkgs/dart_model/benchmark/regular_dart_classes.dart index 80f5692..a352d79 100644 --- a/pkgs/dart_model/benchmark/regular_dart_classes.dart +++ b/pkgs/dart_model/benchmark/regular_dart_classes.dart @@ -2,25 +2,18 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:typed_data'; + import 'package:dart_model/src/json_buffer/json_buffer_builder.dart'; import 'serialization_benchmark.dart'; JsonBufferBuilder? runningBuffer; -/// Benchmark accumulating data directly into a [JsonBufferBuilder] with an -/// indirection through a thin wrapper type (which is a real type, not an -/// extension type). +/// Benchmark for regular dart classes which serialize and deserializ into +/// a [JsonBufferBuilder]. class RegularClassesBufferWireBenchmark extends SerializationBenchmark { @override - void run() { - var data = createData(); - - final buffer = runningBuffer = JsonBufferBuilder(); - data.forEach((k, v) => buffer.map[k] = v.toJson()); - serialized = runningBuffer!.serialize(); - } - /// Creates the data, but its not ready yet to be serialized. Map createData() { final map = {}; @@ -62,12 +55,16 @@ class RegularClassesBufferWireBenchmark extends SerializationBenchmark { } @override - void deserialize() { - deserialized = JsonBufferBuilder.deserialize( - serialized!, - ).map.map( - (k, v) => MapEntry(k, Interface.fromJson(v as Map)), - ); + Map deserialize(Uint8List serialized) => + JsonBufferBuilder.deserialize(serialized).map.map( + (k, v) => MapEntry(k, Interface.fromJson(v as Map)), + ); + + @override + Uint8List serialize(Map data) { + final buffer = runningBuffer = JsonBufferBuilder(); + data.forEach((k, v) => buffer.map[k] = (v as Interface).toJson()); + return runningBuffer!.serialize(); } } diff --git a/pkgs/dart_model/benchmark/sdk_maps_buffer_wire_benchmark.dart b/pkgs/dart_model/benchmark/sdk_maps_buffer_wire_benchmark.dart index 3bf0a49..082114d 100644 --- a/pkgs/dart_model/benchmark/sdk_maps_buffer_wire_benchmark.dart +++ b/pkgs/dart_model/benchmark/sdk_maps_buffer_wire_benchmark.dart @@ -2,6 +2,8 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:typed_data'; + import 'json_buffer.dart'; import 'sdk_maps_json_wire_benchmark.dart'; @@ -9,12 +11,10 @@ import 'sdk_maps_json_wire_benchmark.dart'; /// [JsonBuffer]. class SdkMapsBufferWireBenchmark extends SdkMapsJsonWireBenchmark { @override - void run() { - serialized = JsonBuffer(createData()).serialize(); - } + Uint8List serialize(Map data) => + JsonBuffer(data).serialize(); @override - void deserialize() { - deserialized = JsonBuffer.deserialize(serialized!).asMap; - } + Map deserialize(Uint8List serialized) => + JsonBuffer.deserialize(serialized).asMap; } diff --git a/pkgs/dart_model/benchmark/sdk_maps_builder_wire_benchmark.dart b/pkgs/dart_model/benchmark/sdk_maps_builder_wire_benchmark.dart index 289bddb..ed22e06 100644 --- a/pkgs/dart_model/benchmark/sdk_maps_builder_wire_benchmark.dart +++ b/pkgs/dart_model/benchmark/sdk_maps_builder_wire_benchmark.dart @@ -2,6 +2,8 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:typed_data'; + import 'package:dart_model/src/json_buffer/json_buffer_builder.dart'; import 'sdk_maps_json_wire_benchmark.dart'; @@ -10,14 +12,13 @@ import 'sdk_maps_json_wire_benchmark.dart'; /// [JsonBufferBuilder]. class SdkMapsBuilderWireBenchmark extends SdkMapsJsonWireBenchmark { @override - void run() { + Uint8List serialize(Map data) { final builder = JsonBufferBuilder(); - builder.map.addAll(createData()); - serialized = builder.serialize(); + builder.map.addAll(data); + return builder.serialize(); } @override - void deserialize() { - deserialized = JsonBufferBuilder.deserialize(serialized!).map; - } + Map deserialize(Uint8List serialized) => + JsonBufferBuilder.deserialize(serialized).map; } diff --git a/pkgs/dart_model/benchmark/sdk_maps_json_wire_benchmark.dart b/pkgs/dart_model/benchmark/sdk_maps_json_wire_benchmark.dart index 81c8e38..5e8a35d 100644 --- a/pkgs/dart_model/benchmark/sdk_maps_json_wire_benchmark.dart +++ b/pkgs/dart_model/benchmark/sdk_maps_json_wire_benchmark.dart @@ -10,10 +10,10 @@ import 'serialization_benchmark.dart'; /// Benchmark accumulating data into SDK maps then serializing it to JSON. class SdkMapsJsonWireBenchmark extends SerializationBenchmark { @override - void run() { - serialized = json.fuse(utf8).encode(createData()) as Uint8List; - } + Uint8List serialize(Map data) => + json.fuse(utf8).encode(data) as Uint8List; + @override Map createData() { return Map.fromIterable( mapKeys, @@ -53,8 +53,8 @@ class SdkMapsJsonWireBenchmark extends SerializationBenchmark { } @override - void deserialize() { - deserialized = json.fuse(utf8).decode(serialized!) as Map; + Map deserialize(Uint8List serialized) { + return json.fuse(utf8).decode(serialized) as Map; } } diff --git a/pkgs/dart_model/benchmark/serialization_benchmark.dart b/pkgs/dart_model/benchmark/serialization_benchmark.dart index 8f35dc6..dcabc1b 100644 --- a/pkgs/dart_model/benchmark/serialization_benchmark.dart +++ b/pkgs/dart_model/benchmark/serialization_benchmark.dart @@ -13,69 +13,93 @@ final mapKeys = List.generate(mapSize, (i) => i.toString()); /// Benchmark serializing `dart_model` data. abstract class SerializationBenchmark extends BenchmarkBase { - /// The serialized result; used to report the size. - Uint8List? serialized; + late BenchmarkStage stage; - /// The deserialized result; used to check correctness. - Map? deserialized; + /// The result of [createData]; passed to [serialize] later. + late Map _data; - SerializationBenchmark() : super(''); - - @override - String get name => runtimeType.toString(); + /// The result of [serialize]; used to report the size and passed to + /// [deserialize] later. + late Uint8List _serialized; - /// For [ProcessBenchmark]. - void deserialize(); + /// The result of [deserialize]; used to check correctness and passed to + /// [deepHash] later. + late Map deserialized; - /// Creates a [ProcessBenchmark] based on the deserialized data. - ProcessBenchmark processBenchmark() => ProcessBenchmark(this); + /// The result of [deepHash]; result should be identical from all + /// implementations. + late int hashResult; - List makeMemberNames(int key) { - final length = key % 10; - return List.generate( - // "key % 2999" so some member names are reused. - length, - (i) => 'interface${key % 2999}member$i', - ); - } -} - -/// Benchmark that walks the full deserialized data. -class ProcessBenchmark extends BenchmarkBase { - final SerializationBenchmark serializationBenchmark; - - /// The hash of all the data; used to check correctness. - int? computedResult; + SerializationBenchmark() : super(''); - ProcessBenchmark(this.serializationBenchmark) : super(''); + /// The length of the serialized bytes, only valid to call after running + /// [BenchmarkStage.serialize]. + int get bytes => _serialized.lengthInBytes; @override - String get name => 'Process${serializationBenchmark.name}'; + String get name => '$runtimeType-${stage.name}'; @override void run() { - computedResult = deepHash(serializationBenchmark.deserialized!); + switch (stage) { + case BenchmarkStage.create: + _data = createData(); + case BenchmarkStage.serialize: + _serialized = serialize(_data); + case BenchmarkStage.deserialize: + deserialized = deserialize(_serialized); + case BenchmarkStage.process: + hashResult = deepHash(deserialized); + } } - int deepHash(Map map) { + /// Used to measure [BenchmarkStage.create], sets [_data] to the result. + Map createData(); + + /// Used to measure [BenchmarkStage.serialize], called with [data], sets + /// [_serialized] to the result. + Uint8List serialize(Map data); + + /// Used to measure [BenchmarkStage.deserialize], called with + /// [_serialized], sets [deserialized] to the result. + Map deserialize(Uint8List data); + + /// Used to measure [BenchmarkStage.process], called with + /// [_serialized], sets [deserialized] to the result. + /// + /// Default implementation works only for JSON style maps. + int deepHash(Map deserialized) { var result = 0; - for (final entry in map.entries) { + for (final entry in deserialized.entries) { result ^= entry.key.hashCode; final value = entry.value; - if (value is Map) { - result ^= deepHash(value); - } else if (value is wrapped.Serializable) { - result ^= deepHash(value.toJson()); - } else if (value is Hashable) { - result ^= value.deepHash; - } else { - result ^= value.hashCode; - } + result ^= switch (value) { + Map() => deepHash(value), + Hashable() => value.deepHash, + wrapped.Serializable() => deepHash(value.toJson()), + String() || int() || bool() => value.hashCode, + _ => + throw StateError( + 'Unrecognized JSON value $value, ' + 'custom hash function needed?', + ), + }; } return result; } + + List makeMemberNames(int key) { + final length = key % 10; + return List.generate( + // "key % 2999" so some member names are reused. + length, + (i) => 'interface${key % 2999}member$i', + ); + } } +enum BenchmarkStage { create, serialize, deserialize, process } + /// Interface for computing a hash, when the underlying object isn't a Map. abstract interface class Hashable { int get deepHash;