diff --git a/pkgs/dart_model/benchmark/main.dart b/pkgs/dart_model/benchmark/main.dart index 93dc2f0..c7d0615 100644 --- a/pkgs/dart_model/benchmark/main.dart +++ b/pkgs/dart_model/benchmark/main.dart @@ -10,6 +10,8 @@ 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 'regular_dart_classes.dart'; +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'; @@ -19,6 +21,7 @@ void main() { final lazyMapsBufferWireBenchmark = LazyMapsBufferWireBenchmark(); final lazyWrappersBufferWireBenchmark = LazyWrappersBufferWireBenchmark(); final builderMapsBuilderWireBenchmark = BuilderMapsBuilderWireBenchmark(); + final regularClassesBufferWireBenchmark = RegularClassesBufferWireBenchmark(); final serializationBenchmarks = [ sdkMapsJsonWireBenchmark, SdkMapsBufferWireBenchmark(), @@ -28,6 +31,7 @@ void main() { lazyWrappersBufferWireBenchmark, BuilderMapsJsonWireBenchmark(), builderMapsBuilderWireBenchmark, + regularClassesBufferWireBenchmark, ]; for (var i = 0; i != 3; ++i) { @@ -47,6 +51,7 @@ void main() { lazyMapsBufferWireBenchmark.processBenchmark(), lazyWrappersBufferWireBenchmark.processBenchmark(), builderMapsBuilderWireBenchmark.processBenchmark(), + regularClassesBufferWireBenchmark.processBenchmark(), ]) { final measure = benchmark.measure().toMilliseconds; final paddedName = benchmark.name.padLeft(36); @@ -62,6 +67,10 @@ void main() { deserialized = deserialized.map( (k, v) => MapEntry(k, v.toJson()), ); + } else if (deserialized is Map) { + deserialized = deserialized.map( + (k, v) => MapEntry(k, v.toJson()), + ); } if (!const DeepCollectionEquality().equals( deserialized, diff --git a/pkgs/dart_model/benchmark/regular_dart_classes.dart b/pkgs/dart_model/benchmark/regular_dart_classes.dart new file mode 100644 index 0000000..80f5692 --- /dev/null +++ b/pkgs/dart_model/benchmark/regular_dart_classes.dart @@ -0,0 +1,233 @@ +// 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 '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). +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 = {}; + + for (final key in mapKeys) { + final intKey = int.parse(key); + var interface = Interface( + members: { + for (final memberName in makeMemberNames(intKey)) + memberName: _makeMember(memberName), + }, + 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; + } + + return 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 = JsonBufferBuilder.deserialize( + serialized!, + ).map.map( + (k, v) => MapEntry(k, Interface.fromJson(v as Map)), + ); + } +} + +abstract interface class Serializable { + Map toJson(); +} + +/// An interface. +class Interface implements Serializable, Hashable { + final Map? _members; + Map get members => _members!; + + final Properties? _properties; + Properties get properties => _properties!; + + static TypedMapSchema schema = TypedMapSchema({ + 'members': Type.growableMapPointer, + 'properties': Type.typedMapPointer, + }); + + Interface({Properties? properties, Map? members}) + : _properties = properties, + _members = members; + + factory Interface.fromJson(Map json) => Interface( + properties: Properties.fromJson(json['properties'] as Map), + members: (json['members'] as Map).map( + (k, v) => MapEntry(k, Member.fromJson(v as Map)), + ), + ); + + @override + Map toJson() { + var membersMap = runningBuffer!.createGrowableMap>(); + _members?.forEach((k, v) => membersMap[k] = v.toJson()); + return runningBuffer!.createTypedMap( + schema, + membersMap, + _properties?.toJson(), + ); + } + + @override + int get deepHash { + var result = 0; + result ^= 'members'.hashCode; + _members?.forEach((k, v) { + result ^= k.hashCode; + result ^= v.deepHash; + }); + result ^= 'properties'.hashCode ^ (_properties?.deepHash ?? null.hashCode); + return result; + } +} + +/// A member. +class Member implements Serializable, Hashable { + final Properties? _properties; + Properties get properties => _properties!; + + static TypedMapSchema schema = TypedMapSchema({ + 'properties': Type.typedMapPointer, + }); + + Member({Properties? properties}) : _properties = properties; + + factory Member.fromJson(Map json) => Member( + properties: Properties.fromJson(json['properties'] as Map), + ); + + @override + Map toJson() => + runningBuffer!.createTypedMap(schema, _properties?.toJson()); + + @override + int get deepHash { + var result = 0; + result ^= 'properties'.hashCode ^ (_properties?.deepHash ?? null.hashCode); + return result; + } +} + +/// Set of boolean properties. +class Properties implements Serializable, Hashable { + /// Whether the entity is abstract, meaning it has no definition. + final bool? _isAbstract; + bool get isAbstract => _isAbstract!; + + /// Whether the entity is a class. + final bool? _isClass; + bool get isClass => _isClass!; + + /// Whether the entity is a getter. + final bool? _isGetter; + bool get isGetter => _isGetter!; + + /// Whether the entity is a field. + final bool? _isField; + bool get isField => _isField!; + + /// Whether the entity is a method. + final bool? _isMethod; + bool get isMethod => _isMethod!; + + /// Whether the entity is static. + final bool? _isStatic; + bool get isStatic => _isStatic!; + + static TypedMapSchema schema = TypedMapSchema({ + 'isAbstract': Type.boolean, + 'isClass': Type.boolean, + 'isGetter': Type.boolean, + 'isField': Type.boolean, + 'isMethod': Type.boolean, + 'isStatic': Type.boolean, + }); + + Properties({ + bool? isAbstract, + bool? isClass, + bool? isGetter, + bool? isField, + bool? isMethod, + bool? isStatic, + }) : _isAbstract = isAbstract, + _isClass = isClass, + _isGetter = isGetter, + _isField = isField, + _isMethod = isMethod, + _isStatic = isStatic; + + factory Properties.fromJson(Map json) => Properties( + isAbstract: json['isAbstract'] as bool?, + isClass: json['isClass'] as bool?, + isGetter: json['isGetter'] as bool?, + isField: json['isField'] as bool?, + isMethod: json['isMethod'] as bool?, + isStatic: json['isStatic'] as bool?, + ); + + @override + Map toJson() => runningBuffer!.createTypedMap( + schema, + _isAbstract, + _isClass, + _isGetter, + _isField, + _isMethod, + _isStatic, + ); + + @override + int get deepHash { + var result = 0; + result ^= 'isAbstract'.hashCode ^ _isAbstract.hashCode; + result ^= 'isClass'.hashCode ^ _isClass.hashCode; + result ^= 'isGetter'.hashCode ^ _isGetter.hashCode; + result ^= 'isField'.hashCode ^ _isField.hashCode; + result ^= 'isMethod'.hashCode ^ _isMethod.hashCode; + result ^= 'isStatic'.hashCode ^ _isStatic.hashCode; + return result; + } +} diff --git a/pkgs/dart_model/benchmark/serialization_benchmark.dart b/pkgs/dart_model/benchmark/serialization_benchmark.dart index 588a156..8f35dc6 100644 --- a/pkgs/dart_model/benchmark/serialization_benchmark.dart +++ b/pkgs/dart_model/benchmark/serialization_benchmark.dart @@ -6,7 +6,7 @@ import 'dart:typed_data'; import 'package:benchmark_harness/benchmark_harness.dart'; -import 'lazy_wrappers_buffer_wire_benchmark.dart'; +import 'lazy_wrappers_buffer_wire_benchmark.dart' as wrapped; const mapSize = 10000; final mapKeys = List.generate(mapSize, (i) => i.toString()); @@ -64,8 +64,10 @@ class ProcessBenchmark extends BenchmarkBase { final value = entry.value; if (value is Map) { result ^= deepHash(value); - } else if (value is Serializable) { + } else if (value is wrapped.Serializable) { result ^= deepHash(value.toJson()); + } else if (value is Hashable) { + result ^= value.deepHash; } else { result ^= value.hashCode; } @@ -73,3 +75,8 @@ class ProcessBenchmark extends BenchmarkBase { return result; } } + +/// Interface for computing a hash, when the underlying object isn't a Map. +abstract interface class Hashable { + int get deepHash; +}