Skip to content
This repository was archived by the owner on Feb 4, 2025. It is now read-only.

Commit debc16c

Browse files
authored
add benchmark for real wrapper classes around a JsonBuffer (#151)
This helps us to evaluate how much extension types are actually buying us, by benchmarking against thin wrappers around maps. There are many advantages of real wrappers, such as type checks can actually work, and we can have many different implementations. I have a specific idea in mind here, which is that the analyzer/CFE could provide their own thin wrapper implementations of these types, which wrap the analyzer/CFE representations and implement lazy getters. This would potentially allow us to handle all the query filtering, by just lazily reading the properties we care about into a new (buffer based) representation of the object, based on the fields present in the query. Results are as follows: ``` SdkMapsJsonWireBenchmark: 1896ms, 7177227 bytes SdkMapsBufferWireBenchmark: 1984ms, 5122684 bytes SdkMapsBuilderWireBenchmark: 1631ms, 4859868 bytes LazyMapsJsonWireBenchmark: 1099ms, 7177227 bytes LazyMapsBufferWireBenchmark: 590ms, 5122684 bytes LazyWrappersBufferWireBenchmark: 424ms, 2111761 bytes BuilderMapsJsonWireBenchmark: 1394ms, 7177227 bytes BuilderMapsBuilderWireBenchmark: 396ms, 2111761 bytes ProcessSdkMapsJsonWireBenchmark: 161ms, hash 23186292 ProcessLazyMapsBufferWireBenchmark: 257ms, hash 23186292 ProcessLazyWrappersBufferWireBenchmark: 362ms, hash 23186292 ProcessBuilderMapsBuilderWireBenchmark: 356ms, hash 23186292 ```
1 parent 66baf8b commit debc16c

File tree

3 files changed

+293
-1
lines changed

3 files changed

+293
-1
lines changed
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:collection';
6+
7+
import 'package:dart_model/src/json_buffer/json_buffer_builder.dart';
8+
9+
import 'serialization_benchmark.dart';
10+
11+
JsonBufferBuilder? runningBuffer;
12+
13+
/// Benchmark accumulating data directly into a [JsonBufferBuilder] with an
14+
/// indirection through a thin wrapper type (which is a real type, not an
15+
/// extension type).
16+
class LazyWrappersBufferWireBenchmark extends SerializationBenchmark {
17+
@override
18+
void run() {
19+
createData();
20+
21+
serialized = runningBuffer!.serialize();
22+
}
23+
24+
Map<String, Object?> createData() {
25+
final buffer = runningBuffer = JsonBufferBuilder();
26+
final map = buffer.map;
27+
28+
for (final key in mapKeys) {
29+
final intKey = int.parse(key);
30+
var interface = Interface(
31+
properties: Properties(
32+
isAbstract: (intKey & 1) == 1,
33+
isClass: (intKey & 2) == 2,
34+
isGetter: (intKey & 4) == 4,
35+
isField: (intKey & 8) == 8,
36+
isMethod: (intKey & 16) == 16,
37+
isStatic: (intKey & 32) == 32,
38+
),
39+
);
40+
map[key] = interface.toJson();
41+
var members = interface.members;
42+
for (final memberName in makeMemberNames(intKey)) {
43+
members[memberName] = _makeMember(memberName);
44+
}
45+
}
46+
47+
return buffer.map;
48+
}
49+
50+
Member _makeMember(String key) {
51+
final intKey = key.length;
52+
return Member(
53+
properties: Properties(
54+
isAbstract: (intKey & 1) == 1,
55+
isClass: (intKey & 2) == 2,
56+
isGetter: (intKey & 4) == 4,
57+
isField: const [true, false, null][intKey % 3],
58+
isMethod: (intKey & 16) == 16,
59+
isStatic: (intKey & 32) == 32,
60+
),
61+
);
62+
}
63+
64+
@override
65+
void deserialize() {
66+
deserialized = _LazyMap<Object?, Interface>(
67+
JsonBufferBuilder.deserialize(serialized!).map,
68+
(json) => Interface.fromJson(json as Map<String, Object?>),
69+
(i) => i.toJson(),
70+
);
71+
}
72+
}
73+
74+
class _LazyMap<From, To> extends MapBase<String, To> {
75+
final Map<String, From> _map;
76+
final To Function(From) _convertFrom;
77+
final From Function(To) _convertTo;
78+
79+
_LazyMap(this._map, this._convertFrom, this._convertTo);
80+
81+
@override
82+
To? operator [](Object? key) {
83+
if (_map[key] case var value?) {
84+
return _convertFrom(value as From);
85+
} else {
86+
return null;
87+
}
88+
}
89+
90+
@override
91+
void operator []=(String key, To value) {
92+
_map[key] = _convertTo(value);
93+
}
94+
95+
@override
96+
void clear() {
97+
_map.clear();
98+
}
99+
100+
@override
101+
Iterable<String> get keys => _map.keys;
102+
103+
@override
104+
To? remove(Object? key) {
105+
if (_map.remove(key) case var value?) {
106+
return _convertFrom(value);
107+
} else {
108+
return null;
109+
}
110+
}
111+
112+
@override
113+
Iterable<MapEntry<String, To>> get entries =>
114+
_map.entries.map((e) => MapEntry(e.key, _convertFrom(e.value)));
115+
}
116+
117+
abstract interface class Serializable {
118+
Map<String, Object?> toJson();
119+
}
120+
121+
/// An interface.
122+
abstract interface class Interface implements Serializable {
123+
Map<String, Member> get members;
124+
Properties get properties;
125+
126+
static TypedMapSchema schema = TypedMapSchema({
127+
'members': Type.growableMapPointer,
128+
'properties': Type.typedMapPointer,
129+
});
130+
131+
factory Interface({Properties? properties}) => _JsonInterface(
132+
runningBuffer!.createTypedMap(
133+
schema,
134+
runningBuffer!.createGrowableMap<Map<String, Object?>>(),
135+
properties?.toJson(),
136+
),
137+
);
138+
139+
factory Interface.fromJson(Map<String, Object?> json) => _JsonInterface(json);
140+
}
141+
142+
/// An [Interface] that lazily reads from a map.
143+
class _JsonInterface implements Interface {
144+
final Map<String, Object?> json;
145+
146+
_JsonInterface(this.json);
147+
148+
/// Map of members by name.
149+
@override
150+
Map<String, Member> get members => _LazyMap<Map<String, Object?>, Member>(
151+
(json['members'] as Map<String, Object?>).cast(),
152+
Member.fromJson,
153+
(m) => m.toJson(),
154+
);
155+
156+
/// The properties of this interface.
157+
@override
158+
Properties get properties =>
159+
Properties.fromJson(json['properties'] as Map<String, Object?>);
160+
161+
@override
162+
Map<String, Object?> toJson() => json;
163+
}
164+
165+
/// A member.
166+
abstract interface class Member implements Serializable {
167+
Properties get properties;
168+
169+
static TypedMapSchema schema = TypedMapSchema({
170+
'properties': Type.typedMapPointer,
171+
});
172+
173+
factory Member({Properties? properties}) =>
174+
_JsonMember(runningBuffer!.createTypedMap(schema, properties?.toJson()));
175+
176+
factory Member.fromJson(Map<String, Object?> json) => _JsonMember(json);
177+
}
178+
179+
class _JsonMember implements Member {
180+
final Map<String, Object?> json;
181+
182+
_JsonMember(this.json);
183+
184+
/// The properties of this member.
185+
@override
186+
Properties get properties =>
187+
Properties.fromJson(json['properties'] as Map<String, Object?>);
188+
189+
@override
190+
Map<String, Object?> toJson() => json;
191+
}
192+
193+
/// Set of boolean properties.
194+
abstract interface class Properties implements Serializable {
195+
/// Whether the entity is abstract, meaning it has no definition.
196+
bool get isAbstract;
197+
198+
/// Whether the entity is a class.
199+
bool get isClass;
200+
201+
/// Whether the entity is a getter.
202+
bool get isGetter;
203+
204+
/// Whether the entity is a field.
205+
bool get isField;
206+
207+
/// Whether the entity is a method.
208+
bool get isMethod;
209+
210+
/// Whether the entity is static.
211+
bool get isStatic;
212+
213+
static TypedMapSchema schema = TypedMapSchema({
214+
'isAbstract': Type.boolean,
215+
'isClass': Type.boolean,
216+
'isGetter': Type.boolean,
217+
'isField': Type.boolean,
218+
'isMethod': Type.boolean,
219+
'isStatic': Type.boolean,
220+
});
221+
222+
factory Properties({
223+
bool? isAbstract,
224+
bool? isClass,
225+
bool? isGetter,
226+
bool? isField,
227+
bool? isMethod,
228+
bool? isStatic,
229+
}) => _JsonProperties(
230+
runningBuffer!.createTypedMap(
231+
schema,
232+
isAbstract,
233+
isClass,
234+
isGetter,
235+
isField,
236+
isMethod,
237+
isStatic,
238+
),
239+
);
240+
241+
factory Properties.fromJson(Map<String, Object?> json) =>
242+
_JsonProperties(json);
243+
}
244+
245+
class _JsonProperties implements Properties {
246+
final Map<String, Object?> json;
247+
248+
_JsonProperties(this.json);
249+
250+
/// Whether the entity is abstract, meaning it has no definition.
251+
@override
252+
bool get isAbstract => json['isAbstract'] as bool;
253+
254+
/// Whether the entity is a class.
255+
@override
256+
bool get isClass => json['isClass'] as bool;
257+
258+
/// Whether the entity is a getter.
259+
@override
260+
bool get isGetter => json['isGetter'] as bool;
261+
262+
/// Whether the entity is a field.
263+
@override
264+
bool get isField => json['isField'] as bool;
265+
266+
/// Whether the entity is a method.
267+
@override
268+
bool get isMethod => json['isMethod'] as bool;
269+
270+
/// Whether the entity is static.
271+
@override
272+
bool get isStatic => json['isStatic'] as bool;
273+
274+
@override
275+
Map<String, Object?> toJson() => json;
276+
}

pkgs/dart_model/benchmark/main.dart

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,24 @@ import 'builder_maps_builder_wire_benchmark.dart';
88
import 'builder_maps_json_wire_benchmark.dart';
99
import 'lazy_maps_buffer_wire_benchmark.dart';
1010
import 'lazy_maps_json_wire_benchmark.dart';
11+
import 'lazy_wrappers_buffer_wire_benchmark.dart';
12+
import 'lazy_wrappers_buffer_wire_benchmark.dart' as wrapped;
1113
import 'sdk_maps_buffer_wire_benchmark.dart';
1214
import 'sdk_maps_builder_wire_benchmark.dart';
1315
import 'sdk_maps_json_wire_benchmark.dart';
1416

1517
void main() {
1618
final sdkMapsJsonWireBenchmark = SdkMapsJsonWireBenchmark();
1719
final lazyMapsBufferWireBenchmark = LazyMapsBufferWireBenchmark();
20+
final lazyWrappersBufferWireBenchmark = LazyWrappersBufferWireBenchmark();
1821
final builderMapsBuilderWireBenchmark = BuilderMapsBuilderWireBenchmark();
1922
final serializationBenchmarks = [
2023
sdkMapsJsonWireBenchmark,
2124
SdkMapsBufferWireBenchmark(),
2225
SdkMapsBuilderWireBenchmark(),
2326
LazyMapsJsonWireBenchmark(),
2427
lazyMapsBufferWireBenchmark,
28+
lazyWrappersBufferWireBenchmark,
2529
BuilderMapsJsonWireBenchmark(),
2630
builderMapsBuilderWireBenchmark,
2731
];
@@ -41,6 +45,7 @@ void main() {
4145
for (final benchmark in [
4246
sdkMapsJsonWireBenchmark.processBenchmark(),
4347
lazyMapsBufferWireBenchmark.processBenchmark(),
48+
lazyWrappersBufferWireBenchmark.processBenchmark(),
4449
builderMapsBuilderWireBenchmark.processBenchmark(),
4550
]) {
4651
final measure = benchmark.measure().toMilliseconds;
@@ -51,8 +56,15 @@ void main() {
5156
}
5257

5358
for (final benchmark in serializationBenchmarks.skip(1)) {
59+
var deserialized = benchmark.deserialized;
60+
// Need to unwrap these to compare them as raw maps.
61+
if (deserialized is Map<String, wrapped.Interface>) {
62+
deserialized = deserialized.map<String, Object?>(
63+
(k, v) => MapEntry(k, v.toJson()),
64+
);
65+
}
5466
if (!const DeepCollectionEquality().equals(
55-
benchmark.deserialized,
67+
deserialized,
5668
serializationBenchmarks.first.deserialized,
5769
)) {
5870
throw StateError(

pkgs/dart_model/benchmark/serialization_benchmark.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import 'dart:typed_data';
66

77
import 'package:benchmark_harness/benchmark_harness.dart';
88

9+
import 'lazy_wrappers_buffer_wire_benchmark.dart';
10+
911
const mapSize = 10000;
1012
final mapKeys = List.generate(mapSize, (i) => i.toString());
1113

@@ -62,6 +64,8 @@ class ProcessBenchmark extends BenchmarkBase {
6264
final value = entry.value;
6365
if (value is Map) {
6466
result ^= deepHash(value);
67+
} else if (value is Serializable) {
68+
result ^= deepHash(value.toJson());
6569
} else {
6670
result ^= value.hashCode;
6771
}

0 commit comments

Comments
 (0)