oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1use criterion::*;
2use rand::distributions::Alphanumeric;
3use rand::prelude::StdRng;
4use rand::{Rng, RngCore, SeedableRng};
5use std::cell::Cell;
6use std::collections::HashMap;
7use yrs::encoding::read::{Cursor, Read};
8use yrs::updates::decoder::Decode;
9use yrs::{Array, Doc, Map, MapRef, Text, TextRef, Transact, TransactionMut, Update};
10
11const N: usize = 6000;
12const SQRT_N: usize = 77 * 20;
13const SEED: u64 = 0xdeadbeaf;
14
15enum TextOp {
16 Insert(u32, String),
17 Delete(u32, u32),
18}
19
20enum ArrayOp {
21 Insert(u32, Vec<u32>),
22 Delete(u32, u32),
23}
24
25fn b1_1<R: RngCore>(rng: &mut R, size: usize) -> Vec<TextOp> {
26 let sample: Vec<_> = rng
27 .sample_iter(&Alphanumeric)
28 .take(size)
29 .map(|c| c.to_string())
30 .collect();
31
32 (0..size as u32)
33 .into_iter()
34 .zip(sample)
35 .map(|(i, str)| TextOp::Insert(i, str))
36 .collect()
37}
38
39fn b1_2<R: RngCore>(rng: &mut R, size: usize) -> Vec<TextOp> {
40 let s: String = rng
41 .sample_iter(&Alphanumeric)
42 .take(size)
43 .map(|c| c as char)
44 .collect();
45 vec![TextOp::Insert(0, s)]
46}
47
48fn b1_3<R: RngCore>(rng: &mut R, size: usize) -> Vec<TextOp> {
49 let sample: Vec<_> = rng
50 .sample_iter(&Alphanumeric)
51 .take(size)
52 .map(|c| c.to_string())
53 .collect();
54
55 (0..size as u32)
56 .into_iter()
57 .zip(sample)
58 .map(|(_, str)| TextOp::Insert(0, str))
59 .collect()
60}
61
62fn b1_4<R: RngCore>(rng: &mut R, size: usize) -> Vec<TextOp> {
63 let sample: Vec<_> = rng
64 .sample_iter(&Alphanumeric)
65 .take(size)
66 .map(|c| c.to_string())
67 .collect();
68
69 (0..size as u32)
70 .into_iter()
71 .zip(sample)
72 .map(|(i, str)| {
73 let idx = rng.gen_range(0..i.max(1));
74 TextOp::Insert(idx, str)
75 })
76 .collect()
77}
78
79fn gen_string<R: RngCore>(rng: &mut R, min: usize, max: usize) -> String {
80 let len = rng.gen_range(min..max);
81 rng.sample_iter(&Alphanumeric)
82 .take(len)
83 .map(|x| x as char)
84 .collect()
85}
86
87fn b1_5<R: RngCore>(rng: &mut R, size: usize) -> Vec<TextOp> {
88 (0..size as u32)
89 .into_iter()
90 .map(|i| {
91 let str = gen_string(rng, 2, 10);
92 let idx = rng.gen_range(0..i.max(1));
93 TextOp::Insert(idx, str)
94 })
95 .collect()
96}
97
98fn b1_6<R: RngCore>(rng: &mut R, size: usize) -> Vec<TextOp> {
99 let s: String = rng
100 .sample_iter(&Alphanumeric)
101 .take(size)
102 .map(|x| x as char)
103 .collect();
104 let len = s.len() as u32;
105 vec![TextOp::Insert(0, s), TextOp::Delete(0, len)]
106}
107
108fn b1_7<R: RngCore>(rng: &mut R, size: usize) -> Vec<TextOp> {
109 let total_len = Cell::new(0u32);
110 (0..size as u32)
111 .into_iter()
112 .map(|_| {
113 let total = total_len.get();
114 let idx = if total == 0 {
115 0
116 } else {
117 rng.gen_range(0..total)
118 };
119 if total == idx || rng.gen_bool(0.5) {
120 let str = {
121 let len = rng.gen_range(2..10);
122 total_len.set(total + len);
123 rng.sample_iter(&Alphanumeric)
124 .take(len as usize)
125 .map(|x| x as char)
126 .collect()
127 };
128 TextOp::Insert(idx, str)
129 } else {
130 let hi = (total - idx).min(9);
131 let len = if hi == 1 { 1 } else { rng.gen_range(1..hi) };
132 total_len.set(total - len);
133 TextOp::Delete(idx, len)
134 }
135 })
136 .collect()
137}
138
139fn b1_8<R: RngCore>(rng: &mut R, size: usize) -> Vec<ArrayOp> {
140 let ops: Vec<ArrayOp> = (0..size)
141 .map(|i| ArrayOp::Insert(i as u32, vec![rng.gen()]))
142 .collect();
143 ops
144}
145
146fn b1_9<R: RngCore>(rng: &mut R, size: usize) -> Vec<ArrayOp> {
147 let sample: Vec<u32> = (0..size).map(|_| rng.gen()).collect();
148 vec![ArrayOp::Insert(0, sample)]
149}
150
151fn b1_10<R: RngCore>(rng: &mut R, size: usize) -> Vec<ArrayOp> {
152 (0..size)
153 .map(|_| ArrayOp::Insert(0, vec![rng.gen()]))
154 .collect()
155}
156
157fn b1_11<R: RngCore>(rng: &mut R, size: usize) -> Vec<ArrayOp> {
158 (0..size)
159 .map(|i| {
160 let idx = rng.gen_range(0..(i as u32).max(1));
161 let values = vec![rng.gen()];
162 ArrayOp::Insert(idx, values)
163 })
164 .collect()
165}
166
167fn text_benchmark<F>(c: &mut Criterion, name: &str, gen: F)
168where
169 F: FnOnce(&mut StdRng, usize) -> Vec<TextOp>,
170{
171 let input = {
172 let doc = Doc::new();
173 let txt = doc.get_or_insert_text("text");
174 let mut rng = StdRng::seed_from_u64(SEED);
175 let ops = gen(&mut rng, N);
176 (doc, txt, ops)
177 };
178
179 c.bench_with_input(
180 BenchmarkId::new(name, input.2.len()),
181 &input,
182 |b, (doc, text, ops)| {
183 b.iter(|| {
184 for op in ops.iter() {
185 let mut txn = doc.transact_mut();
186 match op {
187 TextOp::Insert(idx, txt) => text.insert(&mut txn, *idx, txt),
188 TextOp::Delete(idx, len) => text.remove_range(&mut txn, *idx, *len),
189 }
190 }
191 });
192 },
193 );
194}
195
196fn array_benchmark<F>(c: &mut Criterion, name: &str, gen: F)
197where
198 F: FnOnce(&mut StdRng, usize) -> Vec<ArrayOp>,
199{
200 let input = {
201 let doc = Doc::new();
202 let array = doc.get_or_insert_array("text");
203 let mut rng = StdRng::seed_from_u64(SEED);
204 let ops = gen(&mut rng, N);
205 (doc, array, ops)
206 };
207
208 c.bench_with_input(
209 BenchmarkId::new(name, input.2.len()),
210 &input,
211 |b, (doc, array, ops)| {
212 b.iter(|| {
213 for op in ops.iter() {
214 let mut txn = doc.transact_mut();
215 match op {
216 ArrayOp::Insert(idx, values) => {
217 array.insert_range(&mut txn, *idx, values.clone())
218 }
219 ArrayOp::Delete(idx, len) => array.remove_range(&mut txn, *idx, *len),
220 }
221 }
222 });
223 },
224 );
225}
226
227fn concurrent_text_benchmark<F>(c: &mut Criterion, name: &str, gen: F)
228where
229 F: FnOnce(&mut StdRng, usize) -> Vec<(TextOp, TextOp)>,
230{
231 let input = {
232 let d1 = Doc::new();
233 let t1 = d1.get_or_insert_text("text");
234
235 let d2 = Doc::new();
236 let t2 = d2.get_or_insert_text("text");
237
238 let mut rng = StdRng::seed_from_u64(SEED);
239 let ops = gen(&mut rng, N);
240 (d1, t1, d2, t2, ops)
241 };
242
243 fn apply(txn: &mut TransactionMut, txt: &TextRef, op: &TextOp) {
244 match op {
245 TextOp::Insert(idx, content) => txt.insert(txn, *idx, content),
246 TextOp::Delete(idx, len) => txt.remove_range(txn, *idx, *len),
247 }
248 }
249
250 c.bench_with_input(
251 BenchmarkId::new(name, N),
252 &input,
253 |b, (d1, t1, d2, t2, ops)| {
254 b.iter(|| {
255 for (o1, o2) in ops {
256 let mut txn1 = d1.transact_mut();
257 apply(&mut txn1, t1, o1);
258 let u1 = txn1.encode_update_v1();
259
260 let mut txn2 = d2.transact_mut();
261 apply(&mut txn2, t2, o2);
262 let u2 = txn2.encode_update_v1();
263
264 txn1.apply_update(Update::decode_v1(u2.as_slice()).unwrap());
265 txn2.apply_update(Update::decode_v1(u1.as_slice()).unwrap());
266 }
267 });
268 },
269 );
270}
271
272fn b2_1<R: RngCore>(rng: &mut R, size: usize) -> Vec<(TextOp, TextOp)> {
273 let s1 = rng
274 .sample_iter(&Alphanumeric)
275 .take(size)
276 .map(|x| x as char)
277 .collect();
278 let s2 = rng
279 .sample_iter(&Alphanumeric)
280 .take(size)
281 .map(|x| x as char)
282 .collect();
283 let a = TextOp::Insert(0, s1);
284 let b = TextOp::Insert(0, s2);
285 vec![(a, b)]
286}
287
288fn b2_2<R: RngCore>(rng: &mut R, size: usize) -> Vec<(TextOp, TextOp)> {
289 (0..size as u32)
290 .into_iter()
291 .map(|i| {
292 let ca: char = rng.sample_iter(&Alphanumeric).next().unwrap() as char;
293 let ia = rng.gen_range(0..i.max(1));
294
295 let cb: char = rng.sample_iter(&Alphanumeric).next().unwrap() as char;
296 let ib = rng.gen_range(0..i.max(1));
297
298 (
299 TextOp::Insert(ia, ca.to_string()),
300 TextOp::Insert(ib, cb.to_string()),
301 )
302 })
303 .collect()
304}
305
306fn b2_3<R: RngCore>(rng: &mut R, size: usize) -> Vec<(TextOp, TextOp)> {
307 let total_len1 = Cell::new(0u32);
308 let total_len2 = Cell::new(0u32);
309 (0..size as u32)
310 .into_iter()
311 .map(|_| {
312 let t1 = total_len1.get();
313 let i1 = rng.gen_range(0..t1.max(1));
314 let s1 = gen_string(rng, 3, 9);
315 total_len1.set(t1 + s1.len() as u32);
316
317 let t2 = total_len2.get();
318 let i2 = rng.gen_range(0..t2.max(1));
319 let s2 = gen_string(rng, 3, 9);
320 total_len2.set(t2 + s2.len() as u32);
321
322 (TextOp::Insert(i1, s1), TextOp::Insert(i2, s2))
323 })
324 .collect()
325}
326
327fn b2_4<R: RngCore>(rng: &mut R, size: usize) -> Vec<(TextOp, TextOp)> {
328 let mut total_len1 = Cell::new(0u32);
329 let mut total_len2 = Cell::new(0u32);
330
331 fn make_op<R: RngCore>(rng: &mut R, total: &mut Cell<u32>) -> TextOp {
332 let t = total.get();
333 let idx = rng.gen_range(0..t.max(1));
334 if t == idx || rng.gen_bool(0.5) {
335 let str = gen_string(rng, 3, 9);
336 total.set(t + str.len() as u32);
337 TextOp::Insert(idx, str)
338 } else {
339 let hi = (t - idx).min(9);
340 let len = if hi == 1 { 1 } else { rng.gen_range(1..hi) };
341 total.set(t - len);
342 TextOp::Delete(idx, len)
343 }
344 }
345
346 (0..size as u32)
347 .into_iter()
348 .map(|_| {
349 let o1 = make_op(rng, &mut total_len1);
350 let o2 = make_op(rng, &mut total_len2);
351
352 (o1, o2)
353 })
354 .collect()
355}
356
357fn n_concurrent_map_benchmark<F>(c: &mut Criterion, name: &str, f: F)
358where
359 F: Fn(&MapRef, &mut TransactionMut, usize),
360{
361 let input: Vec<_> = (0..SQRT_N)
362 .into_iter()
363 .map(|i| {
364 let doc = Doc::new();
365 let map = doc.get_or_insert_map("map");
366 let update = {
367 let mut txn = doc.transact_mut();
368 f(&map, &mut txn, i);
369 txn.encode_update_v1()
370 };
371 (doc, update)
372 })
373 .collect();
374
375 c.bench_with_input(BenchmarkId::new(name, SQRT_N), &input, |b, input| {
376 b.iter(|| {
377 let mut iter = input.into_iter();
378 let (doc, _) = iter.next().unwrap();
379 let mut txn = doc.transact_mut();
380 while let Some((_, update)) = iter.next() {
381 txn.apply_update(Update::decode_v1(update.as_slice()).unwrap());
382 }
383 });
384 });
385}
386
387fn b3_1(map: &MapRef, txn: &mut TransactionMut, i: usize) {
388 map.insert(txn, "v", i as u32);
389}
390
391fn b3_2(map: &MapRef, txn: &mut TransactionMut, i: usize) {
392 let mut o = HashMap::with_capacity(2);
393 o.insert("name".to_string(), i.to_string());
394 o.insert("address".to_string(), "here".to_string());
395
396 map.insert(txn, "v", o);
397}
398
399fn b3_3(map: &MapRef, txn: &mut TransactionMut, i: usize) {
400 let mut str = String::with_capacity(i * SQRT_N);
401 for _ in 0..SQRT_N {
402 str.push_str(i.to_string().as_str());
403 }
404 map.insert(txn, "v", str);
405}
406
407fn b3_4(c: &mut Criterion, name: &str) {
408 let input: Vec<_> = (0..SQRT_N)
409 .into_iter()
410 .map(|i| {
411 let doc = Doc::new();
412 let array = doc.get_or_insert_array("array");
413 let update = {
414 let mut txn = doc.transact_mut();
415 array.insert(&mut txn, 0, i.to_string());
416 txn.encode_update_v1()
417 };
418 (doc, update)
419 })
420 .collect();
421
422 c.bench_with_input(BenchmarkId::new(name, SQRT_N), &input, |b, input| {
423 b.iter(|| {
424 let mut iter = input.into_iter();
425 let (doc, _) = iter.next().unwrap();
426 let mut txn = doc.transact_mut();
427 while let Some((_, update)) = iter.next() {
428 txn.apply_update(Update::decode_v1(update.as_slice()).unwrap());
429 }
430 });
431 });
432}
433
434fn b4_1(c: &mut Criterion, name: &str) {
435 let doc = Doc::new();
436 let txt = doc.get_or_insert_text("text");
437 let input = read_input("./assets/bench-input/b4-editing-trace.bin");
438
439 c.bench_with_input(
440 BenchmarkId::new(name, input.len()),
441 &(doc, txt, input),
442 |b, (doc, txt, input)| {
443 b.iter(|| {
444 for i in input {
445 let mut txn = doc.transact_mut();
446 match i {
447 TextOp::Insert(idx, chunk) => txt.insert(&mut txn, *idx, chunk),
448 TextOp::Delete(idx, len) => txt.remove_range(&mut txn, *idx, *len),
449 }
450 }
451 });
452 },
453 );
454}
455
456fn b4_2(c: &mut Criterion, name: &str) {
457 let doc = Doc::new();
458 let txt = doc.get_or_insert_text("text");
459 let mut buf = Vec::with_capacity(400 * 1024);
460 let mut f = std::fs::File::open("./assets/bench-input/b4-update.bin").unwrap();
461 std::io::Read::read_to_end(&mut f, &mut buf).unwrap();
462
463 c.bench_with_input(
464 BenchmarkId::new(name, buf.len()),
465 &(doc, txt, buf),
466 |b, (doc, _txt, buf)| {
467 b.iter(|| {
468 let mut txn = doc.transact_mut();
469 txn.apply_update(Update::decode_v1(buf.as_slice()).unwrap());
470 });
471 },
472 );
473}
474
475fn read_input(fpath: &str) -> Vec<TextOp> {
476 use std::fs::File;
477 use yrs::updates::decoder::DecoderV1;
478
479 let mut f = File::open(fpath).unwrap();
480 let mut buf = Vec::new();
481 std::io::Read::read_to_end(&mut f, &mut buf).unwrap();
482 let mut decoder = DecoderV1::new(Cursor::new(buf.as_slice()));
483 let len: usize = decoder.read_var().unwrap();
484 let mut result = Vec::with_capacity(len);
485 for _ in 0..len {
486 let op = {
487 match decoder.read_var().unwrap() {
488 1u32 => {
489 let idx = decoder.read_var().unwrap();
490 let chunk = decoder.read_string().unwrap();
491 TextOp::Insert(idx, chunk.to_string())
492 }
493 2u32 => {
494 let idx = decoder.read_var().unwrap();
495 let len = decoder.read_var().unwrap();
496 TextOp::Delete(idx, len)
497 }
498 other => panic!("unrecognized TextOp tag type: {}", other),
499 }
500 };
501 result.push(op);
502 }
503 result
504}
505
506fn bench(c: &mut Criterion) {
507 text_benchmark(c, "[B1.1] Append N characters", b1_1);
508 text_benchmark(c, "[B1.2] Insert string of length N", b1_2);
509 text_benchmark(c, "[B1.3] Prepend N characters", b1_3);
510 text_benchmark(c, "[B1.4] Insert N characters at random positions", b1_4);
511 text_benchmark(c, "[B1.5] Insert N words at random positions", b1_5);
512 //text_benchmark(c, "[B1.6] Insert string, then delete it", b1_6);
513 text_benchmark(c, "[B1.7] Insert/Delete strings at random positions", b1_7);
514 array_benchmark(c, "[B1.8] Append N numbers", b1_8);
515 array_benchmark(c, "[B1.9] Insert Array of N numbers", b1_9);
516 array_benchmark(c, "[B1.10] Prepend N numbers", b1_10);
517 array_benchmark(c, "[B1.11] Insert N numbers at random positions", b1_11);
518
519 concurrent_text_benchmark(
520 c,
521 "[B2.1] Concurrently insert string of length N at index 0",
522 b2_1,
523 );
524 concurrent_text_benchmark(
525 c,
526 "[B2.2] Concurrently insert N characters at random positions",
527 b2_2,
528 );
529 concurrent_text_benchmark(
530 c,
531 "[B2.3] Concurrently insert N words at random positions",
532 b2_3,
533 );
534 concurrent_text_benchmark(c, "[B2.4] Concurrently insert & delete", b2_4);
535
536 n_concurrent_map_benchmark(
537 c,
538 "[B3.1] 20√N clients concurrently set number in Map",
539 b3_1,
540 );
541 n_concurrent_map_benchmark(
542 c,
543 "[B3.2] 20√N clients concurrently set Object in Map",
544 b3_2,
545 );
546 n_concurrent_map_benchmark(
547 c,
548 "[B3.3] 20√N clients concurrently set String in Map",
549 b3_3,
550 );
551 b3_4(c, "[B3.4] 20√N clients concurrently insert text in Array");
552 b4_2(c, "[B4.2] Apply real-world document snapshot of size");
553 b4_1(c, "[B4.1] Apply real-world editing dataset");
554}
555
556criterion_group! {
557 name = benches;
558 config = Criterion::default().sample_size(10);
559 targets = bench,
560}
561criterion_main!(benches);
562