From 31c646607f0e1f995ce872a97ec3e2a0fb01975e Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Thu, 5 Dec 2024 19:19:56 +0000 Subject: [PATCH 1/2] add benchmark for real wrapper classes around a JsonBuffer --- .../lazy_wrappers_buffer_wire_benchmark.dart | 274 ++++++++++++++++++ pkgs/dart_model/benchmark/main.dart | 14 +- .../benchmark/serialization_benchmark.dart | 4 + 3 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 pkgs/dart_model/benchmark/lazy_wrappers_buffer_wire_benchmark.dart diff --git a/pkgs/dart_model/benchmark/lazy_wrappers_buffer_wire_benchmark.dart b/pkgs/dart_model/benchmark/lazy_wrappers_buffer_wire_benchmark.dart new file mode 100644 index 0000000..05bb234 --- /dev/null +++ b/pkgs/dart_model/benchmark/lazy_wrappers_buffer_wire_benchmark.dart @@ -0,0 +1,274 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// 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:collection'; + +import 'package:dart_model/src/json_buffer/json_buffer_builder.dart'; + +import 'serialization_benchmark.dart'; + +JsonBufferBuilder? runningBuffer; + +/// Benchmark accumulating data directly into a [JsonBufferBuilder]. +class LazyWrappersBufferWireBenchmark extends SerializationBenchmark { + @override + void run() { + createData(); + + serialized = runningBuffer!.serialize(); + } + + Map createData() { + final buffer = runningBuffer = JsonBufferBuilder(); + final map = buffer.map; + + for (final key in mapKeys) { + final intKey = int.parse(key); + var interface = Interface( + properties: Properties( + isAbstract: (intKey & 1) == 1, + isClass: (intKey & 2) == 2, + isGetter: (intKey & 4) == 4, + isField: (intKey & 8) == 8, + isMethod: (intKey & 16) == 16, + isStatic: (intKey & 32) == 32, + ), + ); + map[key] = interface.toJson(); + var members = interface.members; + for (final memberName in makeMemberNames(intKey)) { + members[memberName] = _makeMember(memberName); + } + } + + return buffer.map; + } + + Member _makeMember(String key) { + final intKey = key.length; + return Member( + properties: Properties( + isAbstract: (intKey & 1) == 1, + isClass: (intKey & 2) == 2, + isGetter: (intKey & 4) == 4, + isField: const [true, false, null][intKey % 3], + isMethod: (intKey & 16) == 16, + isStatic: (intKey & 32) == 32, + ), + ); + } + + @override + void deserialize() { + deserialized = _LazyMap( + JsonBufferBuilder.deserialize(serialized!).map, + (json) => Interface.fromJson(json as Map), + (i) => i.toJson(), + ); + } +} + +class _LazyMap extends MapBase { + final Map _map; + final To Function(From) _convertFrom; + final From Function(To) _convertTo; + + _LazyMap(this._map, this._convertFrom, this._convertTo); + + @override + To? operator [](Object? key) { + if (_map[key] case var value?) { + return _convertFrom(value as From); + } else { + return null; + } + } + + @override + void operator []=(String key, To value) { + _map[key] = _convertTo(value); + } + + @override + void clear() { + _map.clear(); + } + + @override + Iterable get keys => _map.keys; + + @override + To? remove(Object? key) { + if (_map.remove(key) case var value?) { + return _convertFrom(value); + } else { + return null; + } + } + + @override + Iterable> get entries => + _map.entries.map((e) => MapEntry(e.key, _convertFrom(e.value))); +} + +abstract interface class Serializable { + Map toJson(); +} + +/// An interface. +abstract interface class Interface implements Serializable { + Map get members; + Properties get properties; + + static TypedMapSchema schema = TypedMapSchema({ + 'members': Type.growableMapPointer, + 'properties': Type.typedMapPointer, + }); + + factory Interface({Properties? properties}) => _JsonInterface( + runningBuffer!.createTypedMap( + schema, + runningBuffer!.createGrowableMap>(), + properties?.toJson(), + ), + ); + + factory Interface.fromJson(Map json) => _JsonInterface(json); +} + +/// An [Interface] that lazily reads from a map. +class _JsonInterface implements Interface { + final Map json; + + _JsonInterface(this.json); + + /// Map of members by name. + @override + Map get members => _LazyMap, Member>( + (json['members'] as Map).cast(), + Member.fromJson, + (m) => m.toJson(), + ); + + /// The properties of this interface. + @override + Properties get properties => + Properties.fromJson(json['properties'] as Map); + + @override + Map toJson() => json; +} + +/// A member. +abstract interface class Member implements Serializable { + Properties get properties; + + static TypedMapSchema schema = TypedMapSchema({ + 'properties': Type.typedMapPointer, + }); + + factory Member({Properties? properties}) => + _JsonMember(runningBuffer!.createTypedMap(schema, properties?.toJson())); + + factory Member.fromJson(Map json) => _JsonMember(json); +} + +class _JsonMember implements Member { + final Map json; + + _JsonMember(this.json); + + /// The properties of this member. + @override + Properties get properties => + Properties.fromJson(json['properties'] as Map); + + @override + Map toJson() => json; +} + +/// Set of boolean properties. +abstract interface class Properties implements Serializable { + /// Whether the entity is abstract, meaning it has no definition. + bool get isAbstract; + + /// Whether the entity is a class. + bool get isClass; + + /// Whether the entity is a getter. + bool get isGetter; + + /// Whether the entity is a field. + bool get isField; + + /// Whether the entity is a method. + bool get isMethod; + + /// Whether the entity is static. + bool get isStatic; + + static TypedMapSchema schema = TypedMapSchema({ + 'isAbstract': Type.boolean, + 'isClass': Type.boolean, + 'isGetter': Type.boolean, + 'isField': Type.boolean, + 'isMethod': Type.boolean, + 'isStatic': Type.boolean, + }); + + factory Properties({ + bool? isAbstract, + bool? isClass, + bool? isGetter, + bool? isField, + bool? isMethod, + bool? isStatic, + }) => _JsonProperties( + runningBuffer!.createTypedMap( + schema, + isAbstract, + isClass, + isGetter, + isField, + isMethod, + isStatic, + ), + ); + + factory Properties.fromJson(Map json) => + _JsonProperties(json); +} + +class _JsonProperties implements Properties { + final Map json; + + _JsonProperties(this.json); + + /// Whether the entity is abstract, meaning it has no definition. + @override + bool get isAbstract => json['isAbstract'] as bool; + + /// Whether the entity is a class. + @override + bool get isClass => json['isClass'] as bool; + + /// Whether the entity is a getter. + @override + bool get isGetter => json['isGetter'] as bool; + + /// Whether the entity is a field. + @override + bool get isField => json['isField'] as bool; + + /// Whether the entity is a method. + @override + bool get isMethod => json['isMethod'] as bool; + + /// Whether the entity is static. + @override + bool get isStatic => json['isStatic'] as bool; + + @override + Map toJson() => json; +} diff --git a/pkgs/dart_model/benchmark/main.dart b/pkgs/dart_model/benchmark/main.dart index 21c7c66..93dc2f0 100644 --- a/pkgs/dart_model/benchmark/main.dart +++ b/pkgs/dart_model/benchmark/main.dart @@ -8,6 +8,8 @@ import 'builder_maps_builder_wire_benchmark.dart'; import 'builder_maps_json_wire_benchmark.dart'; import 'lazy_maps_buffer_wire_benchmark.dart'; import 'lazy_maps_json_wire_benchmark.dart'; +import 'lazy_wrappers_buffer_wire_benchmark.dart'; +import 'lazy_wrappers_buffer_wire_benchmark.dart' as wrapped; import 'sdk_maps_buffer_wire_benchmark.dart'; import 'sdk_maps_builder_wire_benchmark.dart'; import 'sdk_maps_json_wire_benchmark.dart'; @@ -15,6 +17,7 @@ import 'sdk_maps_json_wire_benchmark.dart'; void main() { final sdkMapsJsonWireBenchmark = SdkMapsJsonWireBenchmark(); final lazyMapsBufferWireBenchmark = LazyMapsBufferWireBenchmark(); + final lazyWrappersBufferWireBenchmark = LazyWrappersBufferWireBenchmark(); final builderMapsBuilderWireBenchmark = BuilderMapsBuilderWireBenchmark(); final serializationBenchmarks = [ sdkMapsJsonWireBenchmark, @@ -22,6 +25,7 @@ void main() { SdkMapsBuilderWireBenchmark(), LazyMapsJsonWireBenchmark(), lazyMapsBufferWireBenchmark, + lazyWrappersBufferWireBenchmark, BuilderMapsJsonWireBenchmark(), builderMapsBuilderWireBenchmark, ]; @@ -41,6 +45,7 @@ void main() { for (final benchmark in [ sdkMapsJsonWireBenchmark.processBenchmark(), lazyMapsBufferWireBenchmark.processBenchmark(), + lazyWrappersBufferWireBenchmark.processBenchmark(), builderMapsBuilderWireBenchmark.processBenchmark(), ]) { final measure = benchmark.measure().toMilliseconds; @@ -51,8 +56,15 @@ void main() { } for (final benchmark in serializationBenchmarks.skip(1)) { + var deserialized = benchmark.deserialized; + // Need to unwrap these to compare them as raw maps. + if (deserialized is Map) { + deserialized = deserialized.map( + (k, v) => MapEntry(k, v.toJson()), + ); + } if (!const DeepCollectionEquality().equals( - benchmark.deserialized, + deserialized, serializationBenchmarks.first.deserialized, )) { throw StateError( diff --git a/pkgs/dart_model/benchmark/serialization_benchmark.dart b/pkgs/dart_model/benchmark/serialization_benchmark.dart index d0b0adf..588a156 100644 --- a/pkgs/dart_model/benchmark/serialization_benchmark.dart +++ b/pkgs/dart_model/benchmark/serialization_benchmark.dart @@ -6,6 +6,8 @@ import 'dart:typed_data'; import 'package:benchmark_harness/benchmark_harness.dart'; +import 'lazy_wrappers_buffer_wire_benchmark.dart'; + const mapSize = 10000; final mapKeys = List.generate(mapSize, (i) => i.toString()); @@ -62,6 +64,8 @@ class ProcessBenchmark extends BenchmarkBase { final value = entry.value; if (value is Map) { result ^= deepHash(value); + } else if (value is Serializable) { + result ^= deepHash(value.toJson()); } else { result ^= value.hashCode; } From fbc524edf93a79ac6c5d518c12bed11a407e4591 Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Fri, 6 Dec 2024 16:20:35 +0000 Subject: [PATCH 2/2] longer comment --- .../benchmark/lazy_wrappers_buffer_wire_benchmark.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 05bb234..d92a3e6 100644 --- a/pkgs/dart_model/benchmark/lazy_wrappers_buffer_wire_benchmark.dart +++ b/pkgs/dart_model/benchmark/lazy_wrappers_buffer_wire_benchmark.dart @@ -10,7 +10,9 @@ import 'serialization_benchmark.dart'; JsonBufferBuilder? runningBuffer; -/// Benchmark accumulating data directly into a [JsonBufferBuilder]. +/// Benchmark accumulating data directly into a [JsonBufferBuilder] with an +/// indirection through a thin wrapper type (which is a real type, not an +/// extension type). class LazyWrappersBufferWireBenchmark extends SerializationBenchmark { @override void run() {