Skip to content

Commit 291557a

Browse files
authored
Merge pull request automerge#350 from jeffa5/opt-prop
Optimise prop query
2 parents cc4b839 + 6bf03e0 commit 291557a

File tree

10 files changed

+421
-94
lines changed

10 files changed

+421
-94
lines changed

automerge/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,12 @@ proptest = { version = "^1.0.0", default-features = false, features = ["std"] }
4343
serde_json = { version = "^1.0.73", features=["float_roundtrip"], default-features=true }
4444
maplit = { version = "^1.0" }
4545
decorum = "0.3.1"
46+
criterion = "0.3.5"
47+
48+
[[bench]]
49+
name = "range"
50+
harness = false
51+
52+
[[bench]]
53+
name = "map"
54+
harness = false

automerge/benches/map.rs

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
use automerge::{transaction::Transactable, Automerge, ScalarValue, ROOT};
2+
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
3+
4+
fn repeated_increment(n: u64) -> Automerge {
5+
let mut doc = Automerge::new();
6+
let mut tx = doc.transaction();
7+
tx.put(ROOT, "counter", ScalarValue::counter(0)).unwrap();
8+
for _ in 0..n {
9+
tx.increment(ROOT, "counter", 1).unwrap();
10+
}
11+
tx.commit();
12+
doc
13+
}
14+
15+
fn repeated_put(n: u64) -> Automerge {
16+
let mut doc = Automerge::new();
17+
let mut tx = doc.transaction();
18+
for i in 0..n {
19+
tx.put(ROOT, "0", i).unwrap();
20+
}
21+
tx.commit();
22+
doc
23+
}
24+
25+
fn increasing_put(n: u64) -> Automerge {
26+
let mut doc = Automerge::new();
27+
let mut tx = doc.transaction();
28+
for i in 0..n {
29+
tx.put(ROOT, i.to_string(), i).unwrap();
30+
}
31+
tx.commit();
32+
doc
33+
}
34+
35+
fn decreasing_put(n: u64) -> Automerge {
36+
let mut doc = Automerge::new();
37+
let mut tx = doc.transaction();
38+
for i in (0..n).rev() {
39+
tx.put(ROOT, i.to_string(), i).unwrap();
40+
}
41+
tx.commit();
42+
doc
43+
}
44+
45+
fn criterion_benchmark(c: &mut Criterion) {
46+
let sizes = [100, 1_000, 10_000];
47+
48+
let mut group = c.benchmark_group("map");
49+
for size in &sizes {
50+
group.throughput(criterion::Throughput::Elements(*size));
51+
group.bench_with_input(BenchmarkId::new("repeated put", size), size, |b, &size| {
52+
b.iter(|| repeated_put(size))
53+
});
54+
group.bench_with_input(
55+
BenchmarkId::new("repeated increment", size),
56+
size,
57+
|b, &size| b.iter(|| repeated_increment(size)),
58+
);
59+
60+
group.throughput(criterion::Throughput::Elements(*size));
61+
group.bench_with_input(
62+
BenchmarkId::new("increasing put", size),
63+
size,
64+
|b, &size| b.iter(|| increasing_put(size)),
65+
);
66+
67+
group.throughput(criterion::Throughput::Elements(*size));
68+
group.bench_with_input(
69+
BenchmarkId::new("decreasing put", size),
70+
size,
71+
|b, &size| b.iter(|| decreasing_put(size)),
72+
);
73+
}
74+
group.finish();
75+
76+
let mut group = c.benchmark_group("map save");
77+
for size in &sizes {
78+
group.throughput(criterion::Throughput::Elements(*size));
79+
group.bench_with_input(BenchmarkId::new("repeated put", size), size, |b, &size| {
80+
b.iter_batched(
81+
|| repeated_put(size),
82+
|mut doc| doc.save(),
83+
criterion::BatchSize::LargeInput,
84+
)
85+
});
86+
group.bench_with_input(
87+
BenchmarkId::new("repeated increment", size),
88+
size,
89+
|b, &size| {
90+
b.iter_batched(
91+
|| repeated_increment(size),
92+
|mut doc| doc.save(),
93+
criterion::BatchSize::LargeInput,
94+
)
95+
},
96+
);
97+
98+
group.throughput(criterion::Throughput::Elements(*size));
99+
group.bench_with_input(
100+
BenchmarkId::new("increasing put", size),
101+
size,
102+
|b, &size| {
103+
b.iter_batched(
104+
|| increasing_put(size),
105+
|mut doc| doc.save(),
106+
criterion::BatchSize::LargeInput,
107+
)
108+
},
109+
);
110+
111+
group.throughput(criterion::Throughput::Elements(*size));
112+
group.bench_with_input(
113+
BenchmarkId::new("decreasing put", size),
114+
size,
115+
|b, &size| {
116+
b.iter_batched(
117+
|| decreasing_put(size),
118+
|mut doc| doc.save(),
119+
criterion::BatchSize::LargeInput,
120+
)
121+
},
122+
);
123+
}
124+
group.finish();
125+
126+
let mut group = c.benchmark_group("map load");
127+
for size in &sizes {
128+
group.throughput(criterion::Throughput::Elements(*size));
129+
group.bench_with_input(BenchmarkId::new("repeated put", size), size, |b, &size| {
130+
b.iter_batched(
131+
|| repeated_put(size).save(),
132+
|bytes| Automerge::load(&bytes).unwrap(),
133+
criterion::BatchSize::LargeInput,
134+
)
135+
});
136+
group.bench_with_input(
137+
BenchmarkId::new("repeated increment", size),
138+
size,
139+
|b, &size| {
140+
b.iter_batched(
141+
|| repeated_increment(size).save(),
142+
|bytes| Automerge::load(&bytes).unwrap(),
143+
criterion::BatchSize::LargeInput,
144+
)
145+
},
146+
);
147+
148+
group.throughput(criterion::Throughput::Elements(*size));
149+
group.bench_with_input(
150+
BenchmarkId::new("increasing put", size),
151+
size,
152+
|b, &size| {
153+
b.iter_batched(
154+
|| increasing_put(size).save(),
155+
|bytes| Automerge::load(&bytes).unwrap(),
156+
criterion::BatchSize::LargeInput,
157+
)
158+
},
159+
);
160+
161+
group.throughput(criterion::Throughput::Elements(*size));
162+
group.bench_with_input(
163+
BenchmarkId::new("decreasing put", size),
164+
size,
165+
|b, &size| {
166+
b.iter_batched(
167+
|| decreasing_put(size).save(),
168+
|bytes| Automerge::load(&bytes).unwrap(),
169+
criterion::BatchSize::LargeInput,
170+
)
171+
},
172+
);
173+
}
174+
group.finish();
175+
}
176+
177+
criterion_group!(benches, criterion_benchmark);
178+
criterion_main!(benches);

automerge/benches/range.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use automerge::{transaction::Transactable, Automerge, ROOT};
2+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
3+
4+
fn doc(n: u64) -> Automerge {
5+
let mut doc = Automerge::new();
6+
let mut tx = doc.transaction();
7+
for i in 0..n {
8+
tx.put(ROOT, i.to_string(), i.to_string()).unwrap();
9+
}
10+
tx.commit();
11+
doc
12+
}
13+
14+
fn range(doc: &Automerge) {
15+
let range = doc.values(ROOT);
16+
range.for_each(drop);
17+
}
18+
19+
fn range_rev(doc: &Automerge) {
20+
let range = doc.values(ROOT).rev();
21+
range.for_each(drop);
22+
}
23+
24+
fn range_at(doc: &Automerge) {
25+
let range = doc.values_at(ROOT, &doc.get_heads());
26+
range.for_each(drop);
27+
}
28+
29+
fn range_at_rev(doc: &Automerge) {
30+
let range = doc.values_at(ROOT, &doc.get_heads()).rev();
31+
range.for_each(drop);
32+
}
33+
34+
fn criterion_benchmark(c: &mut Criterion) {
35+
let n = 100_000;
36+
let doc = doc(n);
37+
c.bench_function(&format!("range {}", n), |b| {
38+
b.iter(|| range(black_box(&doc)))
39+
});
40+
c.bench_function(&format!("range rev {}", n), |b| {
41+
b.iter(|| range_rev(black_box(&doc)))
42+
});
43+
c.bench_function(&format!("range_at {}", n), |b| {
44+
b.iter(|| range_at(black_box(&doc)))
45+
});
46+
c.bench_function(&format!("range_at rev {}", n), |b| {
47+
b.iter(|| range_at_rev(black_box(&doc)))
48+
});
49+
}
50+
51+
criterion_group!(benches, criterion_benchmark);
52+
criterion_main!(benches);

automerge/src/op_tree.rs

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88
pub(crate) use crate::op_set::OpSetMetadata;
99
use crate::{
1010
clock::Clock,
11-
query::{self, Index, QueryResult, TreeQuery},
11+
query::{self, Index, QueryResult, ReplaceArgs, TreeQuery},
1212
};
1313
use crate::{
1414
types::{ObjId, Op, OpId},
@@ -105,7 +105,8 @@ impl OpTreeInternal {
105105
self.root_node
106106
.as_ref()
107107
.map(|root| match query.query_node_with_metadata(root, m) {
108-
QueryResult::Descend => root.search(&mut query, m),
108+
QueryResult::Descend => root.search(&mut query, m, None),
109+
QueryResult::Skip(skip) => root.search(&mut query, m, Some(skip)),
109110
_ => true,
110111
});
111112
query
@@ -216,31 +217,62 @@ impl OpTreeNode {
216217
}
217218
}
218219

219-
pub(crate) fn search<'a, 'b: 'a, Q>(&'b self, query: &mut Q, m: &OpSetMetadata) -> bool
220+
pub(crate) fn search<'a, 'b: 'a, Q>(
221+
&'b self,
222+
query: &mut Q,
223+
m: &OpSetMetadata,
224+
skip: Option<usize>,
225+
) -> bool
220226
where
221227
Q: TreeQuery<'a>,
222228
{
223229
if self.is_leaf() {
224-
for e in &self.elements {
230+
let skip = skip.unwrap_or(0);
231+
for e in self.elements.iter().skip(skip) {
225232
if query.query_element_with_metadata(e, m) == QueryResult::Finish {
226233
return true;
227234
}
228235
}
229236
false
230237
} else {
238+
let mut skip = skip.unwrap_or(0);
231239
for (child_index, child) in self.children.iter().enumerate() {
232-
match query.query_node_with_metadata(child, m) {
233-
QueryResult::Descend => {
234-
if child.search(query, m) {
235-
return true;
240+
match skip.cmp(&child.len()) {
241+
Ordering::Greater => {
242+
// not in this child at all
243+
// take off the number of elements in the child as well as the next element
244+
skip -= child.len() + 1;
245+
}
246+
Ordering::Equal => {
247+
// just try the element
248+
skip -= child.len();
249+
if let Some(e) = self.elements.get(child_index) {
250+
if query.query_element_with_metadata(e, m) == QueryResult::Finish {
251+
return true;
252+
}
236253
}
237254
}
238-
QueryResult::Finish => return true,
239-
QueryResult::Next => (),
240-
}
241-
if let Some(e) = self.elements.get(child_index) {
242-
if query.query_element_with_metadata(e, m) == QueryResult::Finish {
243-
return true;
255+
Ordering::Less => {
256+
// descend and try find it
257+
match query.query_node_with_metadata(child, m) {
258+
QueryResult::Descend => {
259+
// search in the child node, passing in the number of items left to
260+
// skip
261+
if child.search(query, m, Some(skip)) {
262+
return true;
263+
}
264+
}
265+
QueryResult::Finish => return true,
266+
QueryResult::Next => (),
267+
QueryResult::Skip(_) => panic!("had skip from non-root node"),
268+
}
269+
if let Some(e) = self.elements.get(child_index) {
270+
if query.query_element_with_metadata(e, m) == QueryResult::Finish {
271+
return true;
272+
}
273+
}
274+
// reset the skip to zero so we continue iterating normally
275+
skip = 0;
244276
}
245277
}
246278
}
@@ -556,16 +588,24 @@ impl OpTreeNode {
556588
/// Update the operation at the given index using the provided function.
557589
///
558590
/// This handles updating the indices after the update.
559-
pub(crate) fn update<F>(&mut self, index: usize, f: F) -> (Op, &Op)
591+
pub(crate) fn update<F>(&mut self, index: usize, f: F) -> ReplaceArgs
560592
where
561593
F: FnOnce(&mut Op),
562594
{
563595
if self.is_leaf() {
564596
let new_element = self.elements.get_mut(index).unwrap();
565-
let old_element = new_element.clone();
597+
let old_id = new_element.id;
598+
let old_visible = new_element.visible();
566599
f(new_element);
567-
self.index.replace(&old_element, new_element);
568-
(old_element, new_element)
600+
let replace_args = ReplaceArgs {
601+
old_id,
602+
new_id: new_element.id,
603+
old_visible,
604+
new_visible: new_element.visible(),
605+
new_key: new_element.elemid_or_key(),
606+
};
607+
self.index.replace(&replace_args);
608+
replace_args
569609
} else {
570610
let mut cumulative_len = 0;
571611
let len = self.len();
@@ -576,15 +616,23 @@ impl OpTreeNode {
576616
}
577617
Ordering::Equal => {
578618
let new_element = self.elements.get_mut(child_index).unwrap();
579-
let old_element = new_element.clone();
619+
let old_id = new_element.id;
620+
let old_visible = new_element.visible();
580621
f(new_element);
581-
self.index.replace(&old_element, new_element);
582-
return (old_element, new_element);
622+
let replace_args = ReplaceArgs {
623+
old_id,
624+
new_id: new_element.id,
625+
old_visible,
626+
new_visible: new_element.visible(),
627+
new_key: new_element.elemid_or_key(),
628+
};
629+
self.index.replace(&replace_args);
630+
return replace_args;
583631
}
584632
Ordering::Greater => {
585-
let (old_element, new_element) = child.update(index - cumulative_len, f);
586-
self.index.replace(&old_element, new_element);
587-
return (old_element, new_element);
633+
let replace_args = child.update(index - cumulative_len, f);
634+
self.index.replace(&replace_args);
635+
return replace_args;
588636
}
589637
}
590638
}

0 commit comments

Comments
 (0)