Skip to content

Commit 2f4f4e7

Browse files
authored
Merge pull request #228 from objectbox/async
Async operations
2 parents 116b3c1 + bf310f9 commit 2f4f4e7

File tree

14 files changed

+639
-67
lines changed

14 files changed

+639
-67
lines changed

benchmark/bin/native_pointers.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ class AsTypedList extends Benchmark {
4444
void setup() => nativePtr = malloc<Uint8>(length);
4545

4646
@override
47-
void teardown() => malloc.free(nativePtr);
47+
void teardown() {
48+
malloc.free(nativePtr);
49+
super.teardown();
50+
}
4851
}
4952

5053
class AsTypedListUint64 extends Benchmark {
@@ -65,5 +68,8 @@ class AsTypedListUint64 extends Benchmark {
6568
void setup() => nativePtr = malloc<Uint64>(length);
6669

6770
@override
68-
void teardown() => malloc.free(nativePtr);
71+
void teardown() {
72+
malloc.free(nativePtr);
73+
super.teardown();
74+
}
6975
}

benchmark/bin/query.dart

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import 'package:objectbox_benchmark/benchmark.dart';
2+
import 'package:objectbox_benchmark/model.dart';
3+
import 'package:objectbox_benchmark/objectbox.g.dart';
4+
5+
const count = 10000;
6+
7+
void main() {
8+
QueryFind().report();
9+
QueryFindIds().report();
10+
QueryStream().report();
11+
}
12+
13+
class QueryBenchmark extends DbBenchmark {
14+
late final Query<TestEntity> query;
15+
16+
QueryBenchmark(String name)
17+
: super(name, iterations: 1, coefficient: 1 / count);
18+
19+
@override
20+
void setup() {
21+
box.putMany(prepareTestEntities(count));
22+
query = box
23+
.query(TestEntity_.tInt
24+
.lessOrEqual((count / 10).floor())
25+
.or(TestEntity_.tInt.greaterThan(count - (count / 10).floor())))
26+
.build();
27+
28+
if (query.count() != count / 5) {
29+
throw Exception('Unexpected number of query results '
30+
'${query.count()} vs expected ${count / 5}');
31+
}
32+
}
33+
34+
@override
35+
void teardown() {
36+
query.close();
37+
super.teardown();
38+
}
39+
}
40+
41+
class QueryFind extends QueryBenchmark {
42+
QueryFind() : super('${QueryFind}');
43+
44+
@override
45+
void run() => query.find();
46+
}
47+
48+
class QueryFindIds extends QueryBenchmark {
49+
QueryFindIds() : super('${QueryFindIds}');
50+
51+
@override
52+
void run() => query.findIds();
53+
}
54+
55+
class QueryStream extends QueryBenchmark {
56+
QueryStream() : super('${QueryStream}');
57+
58+
@override
59+
void run() async => await Future.wait([query.stream().toList()]);
60+
}

benchmark/bin/write.dart

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import 'package:objectbox_benchmark/benchmark.dart';
22
import 'package:objectbox_benchmark/objectbox.g.dart';
33

4+
const count = 10000;
5+
46
void main() {
57
Put().report();
68
PutInTx().report();
79
PutMany().report();
10+
PutAsync().report();
11+
PutAsync2().report();
12+
PutAsync3().report();
13+
PutQueued().report();
814
}
915

1016
class Put extends DbBenchmark {
11-
static const count = 1000;
17+
static const count = 100;
1218
final items = prepareTestEntities(count, assignedIds: true);
1319

1420
Put() : super('${Put}', iterations: count);
@@ -18,27 +24,72 @@ class Put extends DbBenchmark {
1824
}
1925

2026
class PutInTx extends DbBenchmark {
21-
static const count = 1000;
2227
final items = prepareTestEntities(count, assignedIds: true);
2328

24-
PutInTx() : super('${PutInTx}', iterations: count);
29+
PutInTx() : super('${PutInTx}', iterations: 1, coefficient: 1 / count);
2530

2631
@override
27-
void run() {
28-
store.runInTransaction(TxMode.write, () {
29-
for (var i = 0; i < items.length; i++) {
30-
box.put(items[i]);
31-
}
32-
});
33-
}
32+
void runIteration(int i) =>
33+
store.runInTransaction(TxMode.write, () => items.forEach(box.put));
3434
}
3535

3636
class PutMany extends DbBenchmark {
37-
static final count = 10000;
3837
final items = prepareTestEntities(count, assignedIds: true);
3938

4039
PutMany() : super('${PutMany}', iterations: 1, coefficient: 1 / count);
4140

4241
@override
4342
void runIteration(int i) => box.putMany(items);
4443
}
44+
45+
class PutAsync extends DbBenchmark {
46+
final items = prepareTestEntities(count, assignedIds: true);
47+
48+
PutAsync()
49+
: super('${PutAsync}[wait(map())] ',
50+
iterations: 1, coefficient: 1 / count);
51+
52+
@override
53+
void run() async => await Future.wait(items.map(box.putAsync));
54+
}
55+
56+
// This is slightly different (slower) then the [PutAsync] - all futures are
57+
// prepared beforehand, only then it starts to wait for them to complete.
58+
class PutAsync2 extends DbBenchmark {
59+
final items = prepareTestEntities(count, assignedIds: true);
60+
61+
PutAsync2()
62+
: super('${PutAsync2}[wait(map().toList())] ',
63+
iterations: 1, coefficient: 1 / count);
64+
65+
@override
66+
void run() async {
67+
final futures = items.map(box.putAsync).toList(growable: false);
68+
await Future.wait(futures);
69+
store.awaitAsyncSubmitted();
70+
}
71+
}
72+
73+
class PutAsync3 extends DbBenchmark {
74+
final items = prepareTestEntities(count, assignedIds: true);
75+
76+
PutAsync3() : super('${PutAsync3}[wait(putAsync(i))]', iterations: count);
77+
78+
@override
79+
void run() {
80+
items.forEach((item) async => await box.putAsync(item));
81+
store.awaitAsyncSubmitted();
82+
}
83+
}
84+
85+
class PutQueued extends DbBenchmark {
86+
final items = prepareTestEntities(count, assignedIds: true);
87+
88+
PutQueued() : super('${PutQueued}', iterations: count);
89+
90+
@override
91+
void run() {
92+
items.forEach(box.putQueued);
93+
store.awaitAsyncSubmitted();
94+
}
95+
}

benchmark/lib/benchmark.dart

Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,98 @@
1+
import 'dart:cli';
12
import 'dart:io';
23

3-
import 'package:benchmark_harness/benchmark_harness.dart';
4+
import 'package:meta/meta.dart';
45
import 'package:objectbox/objectbox.dart';
56

67
import 'model.dart';
78
import 'objectbox.g.dart';
89

9-
class Benchmark extends BenchmarkBase {
10+
class Benchmark {
11+
final String name;
1012
final int iterations;
13+
final double coefficient;
14+
final watch = Stopwatch();
15+
final Emitter emitter;
16+
17+
Benchmark(this.name, {this.iterations = 1, this.coefficient = 1})
18+
: emitter = Emitter(iterations, coefficient) {
19+
print('-------------------------------------------------------------');
20+
print('$name(iterations): ${Emitter._format(iterations.toDouble())}');
21+
print(
22+
'$name(count): ${Emitter._format(iterations / coefficient)}');
23+
// Measure the total time of the test - if it's too high, you should
24+
// decrease the number of iterations. Expected time is between 2 and 3 sec.
25+
watch.start();
26+
}
1127

12-
Benchmark(String name, {int iterations = 1, double coefficient = 1})
13-
: iterations = iterations,
14-
super(name, emitter: Emitter(iterations, coefficient));
28+
// Not measured setup code executed prior to the benchmark runs.
29+
void setup() {}
1530

16-
@override
17-
void exercise() => run();
31+
@mustCallSuper
32+
void teardown() {
33+
final color = watch.elapsedMilliseconds > 3000 ? '\x1B[31m' : '';
34+
print('$name(total time taken): $color${watch.elapsed.toString()}\x1B[0m');
35+
}
1836

19-
@override
20-
void run() {
37+
void run() async {
2138
for (var i = 0; i < iterations; i++) runIteration(i);
2239
}
2340

24-
void runIteration(int iteration) {}
41+
void runIteration(int iteration) async {}
42+
43+
// Runs [f] for at least [minimumMillis] milliseconds.
44+
static Future<double> _measureFor(Function f, int minimumMillis) async {
45+
final minimumMicros = minimumMillis * 1000;
46+
var iter = 0;
47+
final watch = Stopwatch()..start();
48+
var elapsed = 0;
49+
while (elapsed < minimumMicros) {
50+
await f();
51+
elapsed = watch.elapsedMicroseconds;
52+
iter++;
53+
}
54+
return elapsed / iter;
55+
}
56+
57+
// Measures the score for the benchmark and returns it.
58+
@nonVirtual
59+
Future<double> _measure() async {
60+
setup();
61+
// Warmup for at least 100ms. Discard result.
62+
await _measureFor(run, 100);
63+
// Run the benchmark for at least 2000ms.
64+
var result = await _measureFor(run, 2000);
65+
teardown();
66+
return result;
67+
}
68+
69+
@nonVirtual
70+
void report() {
71+
emitter.emit(name, waitFor(_measure()));
72+
}
2573
}
2674

27-
class Emitter implements ScoreEmitter {
75+
class Emitter {
2876
static const usInSec = 1000000;
2977

3078
final int iterations;
3179
final double coefficient;
3280

3381
const Emitter(this.iterations, this.coefficient);
3482

35-
@override
3683
void emit(String testName, double value) {
3784
final timePerIter = value / iterations;
3885
final timePerUnit = timePerIter * coefficient;
39-
print('$testName(Single iteration): ${format(timePerIter)} us');
40-
print('$testName(Runtime per unit): ${format(timePerUnit)} us');
41-
print('$testName(Runs per second): ${format(usInSec / timePerIter)}');
42-
print('$testName(Units per second): ${format(usInSec / timePerUnit)}');
86+
print('$testName(Single iteration): ${_format(timePerIter)} us');
87+
print('$testName(Runtime per unit): ${_format(timePerUnit)} us');
88+
print('$testName(Runs per second): ${_format(usInSec / timePerIter)}');
89+
print('$testName(Units per second): ${_format(usInSec / timePerUnit)}');
4390
}
4491

4592
// Simple number formatting, maybe use a lib?
4693
// * the smaller the number, the more decimal places it has (one up to four).
4794
// * large numbers use thousands separator (defaults to non-breaking space).
48-
String format(double num, [String thousandsSeparator = ' ']) {
95+
static String _format(double num, [String thousandsSeparator = ' ']) {
4996
final decimalPoints = num < 1
5097
? 4
5198
: num < 10
@@ -72,18 +119,24 @@ class Emitter implements ScoreEmitter {
72119

73120
class DbBenchmark extends Benchmark {
74121
static final String dbDir = 'benchmark-db';
75-
final Store store;
122+
late final Store store;
76123
late final Box<TestEntity> box;
77124

78125
DbBenchmark(String name, {int iterations = 1, double coefficient = 1})
79-
: store = Store(getObjectBoxModel(), directory: dbDir),
80-
super(name, iterations: iterations, coefficient: coefficient) {
126+
: super(name, iterations: iterations, coefficient: coefficient) {
127+
deleteDbDir();
128+
store = Store(getObjectBoxModel(), directory: dbDir);
81129
box = Box<TestEntity>(store);
82130
}
83131

84132
@override
85133
void teardown() {
86134
store.close();
135+
deleteDbDir();
136+
super.teardown();
137+
}
138+
139+
void deleteDbDir() {
87140
final dir = Directory(dbDir);
88141
if (dir.existsSync()) dir.deleteSync(recursive: true);
89142
}

benchmark/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ environment:
77

88
dependencies:
99
objectbox: any
10-
benchmark_harness: any
10+
meta: ^1.3.0
1111

1212
dev_dependencies:
1313
objectbox_generator: any

objectbox/CHANGELOG.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
## latest
22

3-
This is a 1.0 release candidate - we encourage everyone to try it out and provide any last-minute feedback,
4-
especially to new/changed APIs.
3+
This is a 1.0 release candidate - please try it out and give us any last-minute feedback, especially to new and changed APIs.
54

6-
* Query now supports auto-closing. You can still call `close()` manually if you want to free native resources sooner
5+
* New Box `putAsync()` returning a `Future` and `putQueued()` for asynchronous writes.
6+
* Query now supports auto-closing. You can still call `close()` manually if you want to free native resources sooner
77
than they would be by Dart's garbage collector, but it's not mandatory anymore.
88
* Change the "meta-model" fields to provide completely type-safe query building.
99
Conditions you specify are now checked at compile time to match the queried entity.
1010
* Make property queries fully typed, `PropertyQuery.find()` now returns the appropriate `List<...>` type without casts.
11+
* Query conditions `inside()` renamed to `oneOf()`, `notIn()` and `notInList()` renamed to `notOneOf()`.
1112
* Query `stream` and `findStream()` are replaced by `QueryBuilder.watch()`, i.e. `box.query(...).watch()`.
12-
* Query conditions `inside()` renamed to `oneOf()`, `notIn()` and `notInList()` renamed to `notOneOf()`.
13+
* New Query `stream()` to stream objects all the while the query is executed in the background.
1314
* Store `subscribe<EntityType>()` renamed to `watch()`.
1415
* Store `subscribeAll()` replaced by a shared broadcast stream `entityChanges`.
1516
* Entities can now contain `final` fields and they're properly stored/loaded (must be constructor params).

0 commit comments

Comments
 (0)