Skip to content

Commit

Permalink
add benchmarks for normal Dart classes, serialized to JsonBuffers
Browse files Browse the repository at this point in the history
  • Loading branch information
jakemac53 committed Dec 9, 2024
1 parent debc16c commit 770ff0d
Show file tree
Hide file tree
Showing 3 changed files with 251 additions and 2 deletions.
9 changes: 9 additions & 0 deletions pkgs/dart_model/benchmark/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -19,6 +21,7 @@ void main() {
final lazyMapsBufferWireBenchmark = LazyMapsBufferWireBenchmark();
final lazyWrappersBufferWireBenchmark = LazyWrappersBufferWireBenchmark();
final builderMapsBuilderWireBenchmark = BuilderMapsBuilderWireBenchmark();
final regularClassesBufferWireBenchmark = RegularClassesBufferWireBenchmark();
final serializationBenchmarks = [
sdkMapsJsonWireBenchmark,
SdkMapsBufferWireBenchmark(),
Expand All @@ -28,6 +31,7 @@ void main() {
lazyWrappersBufferWireBenchmark,
BuilderMapsJsonWireBenchmark(),
builderMapsBuilderWireBenchmark,
regularClassesBufferWireBenchmark,
];

for (var i = 0; i != 3; ++i) {
Expand All @@ -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);
Expand All @@ -62,6 +67,10 @@ void main() {
deserialized = deserialized.map<String, Object?>(
(k, v) => MapEntry(k, v.toJson()),
);
} else if (deserialized is Map<String, regular.Interface>) {
deserialized = deserialized.map<String, Object?>(
(k, v) => MapEntry(k, v.toJson()),
);
}
if (!const DeepCollectionEquality().equals(
deserialized,
Expand Down
233 changes: 233 additions & 0 deletions pkgs/dart_model/benchmark/regular_dart_classes.dart
Original file line number Diff line number Diff line change
@@ -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<String, Interface> createData() {
final map = <String, Interface>{};

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<String, Interface>(
(k, v) => MapEntry(k, Interface.fromJson(v as Map<String, Object?>)),
);
}
}

abstract interface class Serializable {
Map<String, Object?> toJson();
}

/// An interface.
class Interface implements Serializable, Hashable {
final Map<String, Member>? _members;
Map<String, Member> get members => _members!;

final Properties? _properties;
Properties get properties => _properties!;

static TypedMapSchema schema = TypedMapSchema({
'members': Type.growableMapPointer,
'properties': Type.typedMapPointer,
});

Interface({Properties? properties, Map<String, Member>? members})
: _properties = properties,
_members = members;

factory Interface.fromJson(Map<String, Object?> json) => Interface(
properties: Properties.fromJson(json['properties'] as Map<String, Object?>),
members: (json['members'] as Map<String, Object?>).map(
(k, v) => MapEntry(k, Member.fromJson(v as Map<String, Object?>)),
),
);

@override
Map<String, Object?> toJson() {
var membersMap = runningBuffer!.createGrowableMap<Map<String, Object?>>();
_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<String, Object?> json) => Member(
properties: Properties.fromJson(json['properties'] as Map<String, Object?>),
);

@override
Map<String, Object?> 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<String, Object?> 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<String, Object?> 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;
}
}
11 changes: 9 additions & 2 deletions pkgs/dart_model/benchmark/serialization_benchmark.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -64,12 +64,19 @@ 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;
}
}
return result;
}
}

/// Interface for computing a hash, when the underlying object isn't a Map.
abstract interface class Hashable {
int get deepHash;
}

0 comments on commit 770ff0d

Please sign in to comment.