automerge/rust/automerge/benches/sync.rs
alexjg 08801ab580
automerge-rs: Introduce ReadDoc and SyncDoc traits and add documentation (#511)
The Rust API has so far grown somewhat organically driven by the needs of the
javascript implementation. This has led to an API which is quite awkward and
unfamiliar to Rust programmers. Additionally there is no documentation to speak
of. This commit is the first movement towards cleaning things up a bit. We touch
a lot of files but the changes are all very mechanical. We introduce a few
traits to abstract over the common operations between `Automerge` and
`AutoCommit`, and add a whole bunch of documentation.

* Add a `ReadDoc` trait to describe methods which read value from a document.
  make `Transactable` extend `ReadDoc`
* Add a `SyncDoc` trait to describe methods necessary for synchronizing
  documents.
* Put the `SyncDoc` implementation for `AutoCommit` behind `AutoCommit::sync` to
  ensure that any open transactions are closed before taking part in the sync
  protocol
* Split `OpObserver` into two traits: `OpObserver` + `BranchableObserver`.
  `BranchableObserver` captures the methods which are only needed for observing
  transactions.
* Add a whole bunch of documentation.

The main changes Rust users will need to make is:

* Import the `ReadDoc` trait wherever you are using the methods which have been
  moved to it. Optionally change concrete paramters on functions to `ReadDoc`
  constraints.
* Likewise import the `SyncDoc` trait wherever you are doing synchronisation
  work
* If you are using the `AutoCommit::*_sync_message` methods you will need to add
  a call to `AutoCommit::sync()` first. E.g. `doc.generate_sync_message` becomes
  `doc.sync().generate_sync_message`
* If you have an implementation of `OpObserver` which you are using in an
  `AutoCommit` then split it into an implementation of `OpObserver` and
  `BranchableObserver`
2023-01-30 19:37:03 +00:00

95 lines
2.7 KiB
Rust

use automerge::{
sync::{self, SyncDoc},
transaction::Transactable,
Automerge, ROOT,
};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
#[derive(Default)]
struct DocWithSync {
doc: Automerge,
peer_state: sync::State,
}
impl From<Automerge> for DocWithSync {
fn from(doc: Automerge) -> Self {
Self {
doc,
peer_state: sync::State::default(),
}
}
}
fn increasing_put(n: u64) -> Automerge {
let mut doc = Automerge::new();
let mut tx = doc.transaction();
for i in 0..n {
tx.put(ROOT, i.to_string(), i).unwrap();
}
tx.commit();
doc
}
// keep syncing until doc1 no longer generates a sync message for doc2.
fn sync(doc1: &mut DocWithSync, doc2: &mut DocWithSync) {
while let Some(message1) = doc1.doc.generate_sync_message(&mut doc1.peer_state) {
doc2.doc
.receive_sync_message(&mut doc2.peer_state, message1)
.unwrap();
if let Some(message2) = doc2.doc.generate_sync_message(&mut doc2.peer_state) {
doc1.doc
.receive_sync_message(&mut doc1.peer_state, message2)
.unwrap()
}
}
}
fn criterion_benchmark(c: &mut Criterion) {
let sizes = [100, 1_000, 10_000];
let mut group = c.benchmark_group("sync unidirectional");
for size in &sizes {
group.throughput(criterion::Throughput::Elements(*size));
group.bench_with_input(
BenchmarkId::new("increasing put", size),
size,
|b, &size| {
b.iter_batched(
|| (increasing_put(size), DocWithSync::default()),
|(doc1, mut doc2)| sync(&mut doc1.into(), &mut doc2),
criterion::BatchSize::LargeInput,
)
},
);
}
group.finish();
let mut group = c.benchmark_group("sync unidirectional every change");
for size in &sizes {
group.throughput(criterion::Throughput::Elements(*size));
group.bench_with_input(
BenchmarkId::new("increasing put", size),
size,
|b, &size| {
b.iter(|| {
let mut doc1 = DocWithSync::default();
let mut doc2 = DocWithSync::default();
for i in 0..size {
let mut tx = doc1.doc.transaction();
tx.put(ROOT, i.to_string(), i).unwrap();
tx.commit();
sync(&mut doc1, &mut doc2);
}
})
},
);
}
group.finish();
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);