Skip to content

Commit 0d9868c

Browse files
committed
Add benchmarks for the tree editor and the tree-editor cursor
1 parent 6d1fcca commit 0d9868c

File tree

2 files changed

+195
-0
lines changed

2 files changed

+195
-0
lines changed

gix-object/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ name = "decode-objects"
1919
harness = false
2020
path = "./benches/decode_objects.rs"
2121

22+
[[bench]]
23+
name = "edit-tree"
24+
harness = false
25+
path = "./benches/edit_tree.rs"
26+
2227

2328
[features]
2429
## Data structures implement `serde::Serialize` and `serde::Deserialize`.

gix-object/benches/edit_tree.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput};
2+
use gix_hash::ObjectId;
3+
use gix_hashtable::hash_map::Entry;
4+
use gix_object::tree::EntryKind;
5+
use gix_object::{tree, Tree, WriteTo};
6+
use std::cell::RefCell;
7+
use std::rc::Rc;
8+
9+
fn create_new_tree_add_and_remove(c: &mut Criterion) {
10+
let (storage, mut write) = new_inmemory_writes();
11+
let mut editor = tree::Editor::new(Tree::default(), &gix_object::find::Never, gix_hash::Kind::Sha1);
12+
let mut group = c.benchmark_group("editor");
13+
let small_throughput = Throughput::Elements((1 + 2 + 4) + 3);
14+
group.throughput(small_throughput.clone());
15+
group.bench_function("small tree (empty -> full -> empty)", |b| {
16+
b.iter(|| {
17+
let tree_id = editor
18+
.upsert(Some("file"), EntryKind::Blob, any_blob())
19+
.unwrap()
20+
.upsert(["dir", "file"], EntryKind::Blob, any_blob())
21+
.unwrap()
22+
.upsert(["more", "deeply", "nested", "file"], EntryKind::Blob, any_blob())
23+
.unwrap()
24+
.write(&mut write)
25+
.unwrap();
26+
black_box(tree_id);
27+
let actual = editor
28+
.remove(Some("file"))
29+
.unwrap()
30+
.remove(Some("dir"))
31+
.unwrap()
32+
.remove(Some("more"))
33+
.unwrap()
34+
.write(&mut write)
35+
.unwrap();
36+
assert_eq!(actual, gix_hash::ObjectId::empty_tree(gix_hash::Kind::Sha1));
37+
});
38+
});
39+
40+
let odb = StorageOdb(storage);
41+
let mut editor = tree::Editor::new(Tree::default(), &odb, gix_hash::Kind::Sha1);
42+
let prefixed_throughput = Throughput::Elements((1 + 2 + 4) + 6 * 3 + (3 + 6 * 3));
43+
group.throughput(prefixed_throughput.clone());
44+
group.bench_function("deeply nested tree (empty -> full -> empty)", |b| {
45+
b.iter(|| {
46+
let tree_id = editor
47+
.upsert(["a", "b", "c", "d", "e", "f", "file"], EntryKind::Blob, any_blob())
48+
.unwrap()
49+
.upsert(
50+
["a", "b", "c", "d", "e", "f", "dir", "file"],
51+
EntryKind::Blob,
52+
any_blob(),
53+
)
54+
.unwrap()
55+
.upsert(
56+
["a", "b", "c", "d", "e", "f", "more", "deeply", "nested", "file"],
57+
EntryKind::Blob,
58+
any_blob(),
59+
)
60+
.unwrap()
61+
.write(&mut write)
62+
.unwrap();
63+
black_box(tree_id);
64+
let tree_id = editor
65+
.remove(["a", "b", "c", "d", "e", "f", "file"])
66+
.unwrap()
67+
.remove(["a", "b", "c", "d", "e", "f", "dir"])
68+
.unwrap()
69+
.remove(["a", "b", "c", "d", "e", "f", "more"])
70+
.unwrap()
71+
.write(&mut write)
72+
.unwrap();
73+
black_box(tree_id);
74+
});
75+
});
76+
77+
drop(group);
78+
let mut group = c.benchmark_group("cursor");
79+
group.throughput(small_throughput);
80+
group.bench_function("small tree (empty -> full -> empty)", |b| {
81+
let mut editor = editor.to_cursor();
82+
b.iter(|| {
83+
let tree_id = editor
84+
.upsert(Some("file"), EntryKind::Blob, any_blob())
85+
.unwrap()
86+
.upsert(["dir", "file"], EntryKind::Blob, any_blob())
87+
.unwrap()
88+
.upsert(["more", "deeply", "nested", "file"], EntryKind::Blob, any_blob())
89+
.unwrap()
90+
.write(&mut write)
91+
.unwrap();
92+
black_box(tree_id);
93+
let actual = editor
94+
.remove(Some("file"))
95+
.unwrap()
96+
.remove(Some("dir"))
97+
.unwrap()
98+
.remove(Some("more"))
99+
.unwrap()
100+
.write(&mut write)
101+
.unwrap();
102+
assert_eq!(actual, gix_hash::ObjectId::empty_tree(gix_hash::Kind::Sha1));
103+
});
104+
});
105+
106+
group.throughput(prefixed_throughput);
107+
group.bench_function("deeply nested tree (empty -> full -> empty)", |b| {
108+
let mut editor = editor.cursor_at(["a", "b", "c", "d", "e", "f"]).unwrap();
109+
b.iter(|| {
110+
let tree_id = editor
111+
.upsert(["file"], EntryKind::Blob, any_blob())
112+
.unwrap()
113+
.upsert(["dir", "file"], EntryKind::Blob, any_blob())
114+
.unwrap()
115+
.upsert(["more", "deeply", "nested", "file"], EntryKind::Blob, any_blob())
116+
.unwrap()
117+
.write(&mut write)
118+
.unwrap();
119+
black_box(tree_id);
120+
let actual = editor
121+
.remove(["file"])
122+
.unwrap()
123+
.remove(["dir"])
124+
.unwrap()
125+
.remove(["more"])
126+
.unwrap()
127+
.write(&mut write)
128+
.unwrap();
129+
assert_eq!(actual, gix_hash::ObjectId::empty_tree(gix_hash::Kind::Sha1));
130+
});
131+
});
132+
}
133+
134+
criterion_group!(benches, create_new_tree_add_and_remove);
135+
criterion_main!(benches);
136+
137+
type TreeStore = Rc<RefCell<gix_hashtable::HashMap<ObjectId, Tree>>>;
138+
139+
fn new_inmemory_writes() -> (TreeStore, impl FnMut(&Tree) -> Result<ObjectId, std::io::Error>) {
140+
let store = TreeStore::default();
141+
let write_tree = {
142+
let store = store.clone();
143+
let mut buf = Vec::with_capacity(512);
144+
move |tree: &Tree| {
145+
buf.clear();
146+
tree.write_to(&mut buf)?;
147+
let header = gix_object::encode::loose_header(gix_object::Kind::Tree, buf.len() as u64);
148+
let mut hasher = gix_features::hash::hasher(gix_hash::Kind::Sha1);
149+
hasher.update(&header);
150+
hasher.update(&buf);
151+
let id = hasher.digest().into();
152+
let mut borrowed = store.borrow_mut();
153+
match borrowed.entry(id) {
154+
Entry::Occupied(_) => {}
155+
Entry::Vacant(e) => {
156+
e.insert(tree.clone());
157+
}
158+
};
159+
Ok(id)
160+
}
161+
};
162+
(store, write_tree)
163+
}
164+
165+
struct StorageOdb(TreeStore);
166+
167+
impl gix_object::Find for StorageOdb {
168+
fn try_find<'a>(
169+
&self,
170+
id: &gix_hash::oid,
171+
buffer: &'a mut Vec<u8>,
172+
) -> Result<Option<gix_object::Data<'a>>, gix_object::find::Error> {
173+
let borrow = self.0.borrow();
174+
match borrow.get(id) {
175+
None => Ok(None),
176+
Some(tree) => {
177+
buffer.clear();
178+
tree.write_to(buffer).expect("valid trees can always be serialized");
179+
Ok(Some(gix_object::Data {
180+
kind: gix_object::Kind::Tree,
181+
data: &*buffer,
182+
}))
183+
}
184+
}
185+
}
186+
}
187+
188+
fn any_blob() -> ObjectId {
189+
ObjectId::from_hex("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".as_bytes()).unwrap()
190+
}

0 commit comments

Comments
 (0)