mirror of
https://github.com/loro-dev/loro.git
synced 2025-01-22 12:57:20 +00:00
Add benchmark utils that simulate drawing workflow (#229)
* chore: add benchmark utils that simulate drawing workflow * chore: use markdown as table default style * chore: init sheet simulating
This commit is contained in:
parent
6a2d0f8fef
commit
727b5c2518
14 changed files with 604 additions and 79 deletions
50
Cargo.lock
generated
50
Cargo.lock
generated
|
@ -105,6 +105,15 @@ dependencies = [
|
|||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "benches"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bench-utils",
|
||||
"loro",
|
||||
"tabled 0.15.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
|
@ -798,7 +807,7 @@ dependencies = [
|
|||
"smallvec",
|
||||
"static_assertions",
|
||||
"string_cache",
|
||||
"tabled",
|
||||
"tabled 0.10.0",
|
||||
"thiserror",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
@ -1035,6 +1044,17 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "papergrid"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ad43c07024ef767f9160710b3a6773976194758c7919b17e63b863db0bdf7fb"
|
||||
dependencies = [
|
||||
"bytecount",
|
||||
"fnv",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
|
@ -1645,8 +1665,19 @@ version = "0.10.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56c3ee73732ffceaea7b8f6b719ce3bb17f253fa27461ffeaf568ebd0cdb4b85"
|
||||
dependencies = [
|
||||
"papergrid",
|
||||
"tabled_derive",
|
||||
"papergrid 0.7.1",
|
||||
"tabled_derive 0.5.0",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tabled"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c998b0c8b921495196a48aabaf1901ff28be0760136e31604f7967b0792050e"
|
||||
dependencies = [
|
||||
"papergrid 0.11.0",
|
||||
"tabled_derive 0.7.0",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
|
@ -1663,6 +1694,19 @@ dependencies = [
|
|||
"syn 1.0.107",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tabled_derive"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c138f99377e5d653a371cdad263615634cfc8467685dfe8e73e2b8e98f44b17"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
"proc-macro2 1.0.67",
|
||||
"quote 1.0.29",
|
||||
"syn 1.0.107",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.12.5"
|
||||
|
|
|
@ -1,33 +1,27 @@
|
|||
use arbitrary::{Arbitrary, Unstructured};
|
||||
use arbitrary::Arbitrary;
|
||||
|
||||
#[derive(Arbitrary)]
|
||||
#[derive(Debug, Arbitrary, PartialEq, Eq)]
|
||||
pub struct Point {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
}
|
||||
|
||||
#[derive(Arbitrary)]
|
||||
#[derive(Debug, Arbitrary, PartialEq, Eq)]
|
||||
pub enum DrawAction {
|
||||
DrawPath {
|
||||
CreatePath {
|
||||
points: Vec<Point>,
|
||||
color: i32,
|
||||
},
|
||||
Text {
|
||||
id: i32,
|
||||
text: String,
|
||||
pos: Point,
|
||||
width: i32,
|
||||
height: i32,
|
||||
size: Point,
|
||||
},
|
||||
CreateRect {
|
||||
pos: Point,
|
||||
size: Point,
|
||||
},
|
||||
Move {
|
||||
id: u32,
|
||||
relative_to: Point,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn gen_draw_actions(seed: u64, num: usize) -> Vec<DrawAction> {
|
||||
let be_bytes = seed.to_be_bytes();
|
||||
let mut gen = Unstructured::new(&be_bytes);
|
||||
let mut ans = vec![];
|
||||
for _ in 0..num {
|
||||
ans.push(gen.arbitrary().unwrap());
|
||||
}
|
||||
|
||||
ans
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
pub mod draw;
|
||||
use arbitrary::Arbitrary;
|
||||
pub mod sheet;
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
use enum_as_inner::EnumAsInner;
|
||||
use rand::{rngs::StdRng, RngCore, SeedableRng};
|
||||
use rand::{RngCore, SeedableRng};
|
||||
use std::io::Read;
|
||||
|
||||
use flate2::read::GzDecoder;
|
||||
|
@ -45,19 +46,20 @@ pub fn get_automerge_actions() -> Vec<TextAction> {
|
|||
actions
|
||||
}
|
||||
|
||||
#[derive(EnumAsInner, Arbitrary)]
|
||||
pub enum Action {
|
||||
Text { client: usize, action: TextAction },
|
||||
#[derive(Debug, EnumAsInner, Arbitrary, PartialEq, Eq)]
|
||||
pub enum Action<T> {
|
||||
Action { peer: usize, action: T },
|
||||
Sync { from: usize, to: usize },
|
||||
SyncAll,
|
||||
}
|
||||
|
||||
pub fn gen_realtime_actions(action_num: usize, client_num: usize, seed: u64) -> Vec<Action> {
|
||||
let mut gen = StdRng::seed_from_u64(seed);
|
||||
let size = Action::size_hint(1);
|
||||
let size = size.1.unwrap_or(size.0);
|
||||
let mut dest = vec![0; action_num * size];
|
||||
gen.fill_bytes(&mut dest);
|
||||
let mut arb = arbitrary::Unstructured::new(&dest);
|
||||
pub fn gen_realtime_actions<'a, T: Arbitrary<'a>>(
|
||||
action_num: usize,
|
||||
peer_num: usize,
|
||||
seed: &'a [u8],
|
||||
mut preprocess: impl FnMut(&mut Action<T>),
|
||||
) -> Result<Vec<Action<T>>, Box<str>> {
|
||||
let mut arb = Unstructured::new(seed);
|
||||
let mut ans = Vec::new();
|
||||
let mut last_sync_all = 0;
|
||||
for i in 0..action_num {
|
||||
|
@ -65,25 +67,82 @@ pub fn gen_realtime_actions(action_num: usize, client_num: usize, seed: u64) ->
|
|||
break;
|
||||
}
|
||||
|
||||
let mut action = arb.arbitrary().unwrap();
|
||||
let mut action: Action<T> = arb
|
||||
.arbitrary()
|
||||
.map_err(|e| e.to_string().into_boxed_str())?;
|
||||
match &mut action {
|
||||
Action::Text { client, action } => {
|
||||
*client %= client_num;
|
||||
if !action.ins.is_empty() {
|
||||
action.ins = (action.ins.as_bytes()[0]).to_string();
|
||||
}
|
||||
Action::Action { peer, .. } => {
|
||||
*peer %= peer_num;
|
||||
}
|
||||
Action::SyncAll => {
|
||||
last_sync_all = i;
|
||||
}
|
||||
Action::Sync { from, to } => {
|
||||
*from %= peer_num;
|
||||
*to %= peer_num;
|
||||
}
|
||||
}
|
||||
|
||||
preprocess(&mut action);
|
||||
ans.push(action);
|
||||
if i - last_sync_all > 100 {
|
||||
if i - last_sync_all > 10 {
|
||||
ans.push(Action::SyncAll);
|
||||
last_sync_all = i;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ans)
|
||||
}
|
||||
|
||||
pub fn gen_async_actions<'a, T: Arbitrary<'a>>(
|
||||
action_num: usize,
|
||||
peer_num: usize,
|
||||
seed: &'a [u8],
|
||||
actions_before_sync: usize,
|
||||
mut preprocess: impl FnMut(&mut Action<T>),
|
||||
) -> Result<Vec<Action<T>>, Box<str>> {
|
||||
let mut arb = Unstructured::new(seed);
|
||||
let mut ans = Vec::new();
|
||||
let mut last_sync_all = 0;
|
||||
while ans.len() < action_num {
|
||||
if ans.len() >= action_num {
|
||||
break;
|
||||
}
|
||||
|
||||
if arb.is_empty() {
|
||||
return Err("not enough actions".into());
|
||||
}
|
||||
|
||||
let mut action: Action<T> = arb
|
||||
.arbitrary()
|
||||
.map_err(|e| e.to_string().into_boxed_str())?;
|
||||
match &mut action {
|
||||
Action::Action { peer, .. } => {
|
||||
*peer %= peer_num;
|
||||
}
|
||||
Action::SyncAll => {
|
||||
if ans.len() - last_sync_all < actions_before_sync {
|
||||
continue;
|
||||
}
|
||||
|
||||
last_sync_all = ans.len();
|
||||
}
|
||||
Action::Sync { from, to } => {
|
||||
*from %= peer_num;
|
||||
*to %= peer_num;
|
||||
}
|
||||
}
|
||||
|
||||
preprocess(&mut action);
|
||||
ans.push(action);
|
||||
}
|
||||
|
||||
Ok(ans)
|
||||
}
|
||||
|
||||
pub fn create_seed(seed: u64, size: usize) -> Vec<u8> {
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
|
||||
let mut ans = vec![0; size];
|
||||
rng.fill_bytes(&mut ans);
|
||||
ans
|
||||
}
|
||||
|
|
37
crates/bench-utils/src/sheet.rs
Normal file
37
crates/bench-utils/src/sheet.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use arbitrary::Arbitrary;
|
||||
|
||||
#[derive(Debug, Arbitrary, PartialEq, Eq)]
|
||||
pub enum SheetAction {
|
||||
SetValue {
|
||||
row: usize,
|
||||
col: usize,
|
||||
value: usize,
|
||||
},
|
||||
InsertRow {
|
||||
row: usize,
|
||||
},
|
||||
InsertCol {
|
||||
col: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl SheetAction {
|
||||
pub const MAX_ROW: usize = 1_048_576;
|
||||
pub const MAX_COL: usize = 16_384;
|
||||
/// Excel has a limit of 1,048,576 rows and 16,384 columns per sheet.
|
||||
// We need to normalize the action to fit the limit.
|
||||
pub fn normalize(&mut self) {
|
||||
match self {
|
||||
SheetAction::SetValue { row, col, .. } => {
|
||||
*row %= Self::MAX_ROW;
|
||||
*col %= Self::MAX_COL;
|
||||
}
|
||||
SheetAction::InsertRow { row } => {
|
||||
*row %= Self::MAX_ROW;
|
||||
}
|
||||
SheetAction::InsertCol { col } => {
|
||||
*col %= Self::MAX_COL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
crates/benches/Cargo.toml
Normal file
11
crates/benches/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "benches"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bench-utils = { path = "../bench-utils" }
|
||||
loro = { path = "../loro" }
|
||||
tabled = "0.15.0"
|
136
crates/benches/examples/draw.rs
Normal file
136
crates/benches/examples/draw.rs
Normal file
|
@ -0,0 +1,136 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use benches::draw::{run_async_draw_workflow, run_realtime_collab_draw_workflow};
|
||||
use loro::LoroDoc;
|
||||
use tabled::{settings::Style, Table, Tabled};
|
||||
|
||||
#[derive(Tabled)]
|
||||
struct BenchResult {
|
||||
task: &'static str,
|
||||
action_size: usize,
|
||||
peer_num: usize,
|
||||
ops_num: usize,
|
||||
changes_num: usize,
|
||||
snapshot_size: usize,
|
||||
updates_size: usize,
|
||||
apply_duration: f64,
|
||||
encode_snapshot_duration: f64,
|
||||
encode_udpate_duration: f64,
|
||||
decode_snapshot_duration: f64,
|
||||
decode_update_duration: f64,
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let seed = 123123;
|
||||
let ans = vec![
|
||||
run_async(1, 100, seed),
|
||||
run_async(1, 1000, seed),
|
||||
run_async(1, 10000, seed),
|
||||
run_async(5, 100, seed),
|
||||
run_async(5, 1000, seed),
|
||||
run_async(5, 10000, seed),
|
||||
run_async(10, 1000, seed),
|
||||
run_async(10, 10000, seed),
|
||||
run_async(10, 100000, seed),
|
||||
run_async(10, 100000, 1000),
|
||||
run_realtime_collab(5, 100, seed),
|
||||
run_realtime_collab(5, 1000, seed),
|
||||
run_realtime_collab(5, 10000, seed),
|
||||
run_realtime_collab(10, 1000, seed),
|
||||
run_realtime_collab(10, 10000, seed),
|
||||
run_realtime_collab(10, 100000, seed),
|
||||
run_realtime_collab(10, 100000, 1000),
|
||||
];
|
||||
let mut table = Table::new(ans);
|
||||
let style = Style::markdown();
|
||||
table.with(style);
|
||||
println!("{}", table);
|
||||
}
|
||||
|
||||
fn run_async(peer_num: usize, action_num: usize, seed: u64) -> BenchResult {
|
||||
eprintln!(
|
||||
"run_async(peer_num: {}, action_num: {})",
|
||||
peer_num, action_num
|
||||
);
|
||||
let (mut actors, start) = run_async_draw_workflow(peer_num, action_num, 200, seed);
|
||||
actors.sync_all();
|
||||
let apply_duration = start.elapsed().as_secs_f64() * 1000.;
|
||||
|
||||
let start = Instant::now();
|
||||
let snapshot = actors.docs[0].doc.export_snapshot();
|
||||
let encode_snapshot_duration = start.elapsed().as_secs_f64() * 1000.;
|
||||
let snapshot_size = snapshot.len();
|
||||
|
||||
let start = Instant::now();
|
||||
let updates = actors.docs[0].doc.export_from(&Default::default());
|
||||
let encode_udpate_duration = start.elapsed().as_secs_f64() * 1000.;
|
||||
let updates_size = updates.len();
|
||||
|
||||
let start = Instant::now();
|
||||
let doc = LoroDoc::new();
|
||||
doc.import(&snapshot).unwrap();
|
||||
let decode_snapshot_duration = start.elapsed().as_secs_f64() * 1000.;
|
||||
|
||||
let start = Instant::now();
|
||||
doc.import(&updates).unwrap();
|
||||
let decode_update_duration = start.elapsed().as_secs_f64() * 1000.;
|
||||
|
||||
BenchResult {
|
||||
task: "async draw",
|
||||
action_size: action_num,
|
||||
peer_num,
|
||||
snapshot_size,
|
||||
ops_num: actors.docs[0].doc.len_ops(),
|
||||
changes_num: actors.docs[0].doc.len_changes(),
|
||||
updates_size,
|
||||
apply_duration,
|
||||
encode_snapshot_duration,
|
||||
encode_udpate_duration,
|
||||
decode_snapshot_duration,
|
||||
decode_update_duration,
|
||||
}
|
||||
}
|
||||
|
||||
fn run_realtime_collab(peer_num: usize, action_num: usize, seed: u64) -> BenchResult {
|
||||
eprintln!(
|
||||
"run_realtime_collab(peer_num: {}, action_num: {})",
|
||||
peer_num, action_num
|
||||
);
|
||||
let (mut actors, start) = run_realtime_collab_draw_workflow(peer_num, action_num, seed);
|
||||
actors.sync_all();
|
||||
let apply_duration = start.elapsed().as_secs_f64() * 1000.;
|
||||
|
||||
let start = Instant::now();
|
||||
let snapshot = actors.docs[0].doc.export_snapshot();
|
||||
let encode_snapshot_duration = start.elapsed().as_secs_f64() * 1000.;
|
||||
let snapshot_size = snapshot.len();
|
||||
|
||||
let start = Instant::now();
|
||||
let updates = actors.docs[0].doc.export_from(&Default::default());
|
||||
let encode_udpate_duration = start.elapsed().as_secs_f64() * 1000.;
|
||||
let updates_size = updates.len();
|
||||
|
||||
let start = Instant::now();
|
||||
let doc = LoroDoc::new();
|
||||
doc.import(&snapshot).unwrap();
|
||||
let decode_snapshot_duration = start.elapsed().as_secs_f64() * 1000.;
|
||||
|
||||
let start = Instant::now();
|
||||
doc.import(&updates).unwrap();
|
||||
let decode_update_duration = start.elapsed().as_secs_f64() * 1000.;
|
||||
|
||||
BenchResult {
|
||||
task: "realtime draw",
|
||||
action_size: action_num,
|
||||
peer_num,
|
||||
ops_num: actors.docs[0].doc.len_ops(),
|
||||
changes_num: actors.docs[0].doc.len_changes(),
|
||||
snapshot_size,
|
||||
updates_size,
|
||||
apply_duration,
|
||||
encode_snapshot_duration,
|
||||
encode_udpate_duration,
|
||||
decode_snapshot_duration,
|
||||
decode_update_duration,
|
||||
}
|
||||
}
|
14
crates/benches/examples/init_sheet.rs
Normal file
14
crates/benches/examples/init_sheet.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
use benches::sheet::init_sheet;
|
||||
use std::time::Instant;
|
||||
|
||||
pub fn main() {
|
||||
let start = Instant::now();
|
||||
let doc = init_sheet();
|
||||
let init_duration = start.elapsed().as_secs_f64() * 1000.;
|
||||
println!("init_duration {}", init_duration);
|
||||
|
||||
let start = Instant::now();
|
||||
let snapshot = doc.export_snapshot();
|
||||
let duration = start.elapsed().as_secs_f64() * 1000.;
|
||||
println!("export duration {} size={}", duration, snapshot.len());
|
||||
}
|
200
crates/benches/src/draw.rs
Normal file
200
crates/benches/src/draw.rs
Normal file
|
@ -0,0 +1,200 @@
|
|||
use std::{collections::HashMap, time::Instant};
|
||||
|
||||
use bench_utils::{create_seed, draw::DrawAction, gen_async_actions, gen_realtime_actions, Action};
|
||||
use loro::{ContainerID, ContainerType};
|
||||
|
||||
pub struct DrawActor {
|
||||
pub doc: loro::LoroDoc,
|
||||
paths: loro::LoroList,
|
||||
texts: loro::LoroList,
|
||||
rects: loro::LoroList,
|
||||
id_to_obj: HashMap<usize, ContainerID>,
|
||||
}
|
||||
|
||||
impl DrawActor {
|
||||
pub fn new(id: u64) -> Self {
|
||||
let doc = loro::LoroDoc::new();
|
||||
doc.set_peer_id(id).unwrap();
|
||||
let paths = doc.get_list("all_paths");
|
||||
let texts = doc.get_list("all_texts");
|
||||
let rects = doc.get_list("all_rects");
|
||||
let id_to_obj = HashMap::new();
|
||||
Self {
|
||||
doc,
|
||||
paths,
|
||||
texts,
|
||||
rects,
|
||||
id_to_obj,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_action(&mut self, action: &mut DrawAction) {
|
||||
match action {
|
||||
DrawAction::CreatePath { points } => {
|
||||
let path = self.paths.insert_container(0, ContainerType::Map).unwrap();
|
||||
let path_map = path.into_map().unwrap();
|
||||
let pos_map = path_map
|
||||
.insert_container("pos", ContainerType::Map)
|
||||
.unwrap()
|
||||
.into_map()
|
||||
.unwrap();
|
||||
pos_map.insert("x", 0).unwrap();
|
||||
pos_map.insert("y", 0).unwrap();
|
||||
let path = path_map
|
||||
.insert_container("path", ContainerType::List)
|
||||
.unwrap()
|
||||
.into_list()
|
||||
.unwrap();
|
||||
for p in points {
|
||||
let map = path
|
||||
.push_container(ContainerType::Map)
|
||||
.unwrap()
|
||||
.into_map()
|
||||
.unwrap();
|
||||
map.insert("x", p.x).unwrap();
|
||||
map.insert("y", p.y).unwrap();
|
||||
}
|
||||
let len = self.id_to_obj.len();
|
||||
self.id_to_obj.insert(len, path.id());
|
||||
}
|
||||
DrawAction::Text { text, pos, size } => {
|
||||
let text_container = self
|
||||
.texts
|
||||
.insert_container(0, ContainerType::Map)
|
||||
.unwrap()
|
||||
.into_map()
|
||||
.unwrap();
|
||||
let text_inner = text_container
|
||||
.insert_container("text", ContainerType::Text)
|
||||
.unwrap()
|
||||
.into_text()
|
||||
.unwrap();
|
||||
text_inner.insert(0, text).unwrap();
|
||||
let map = text_container
|
||||
.insert_container("pos", ContainerType::Map)
|
||||
.unwrap()
|
||||
.into_map()
|
||||
.unwrap();
|
||||
map.insert("x", pos.x).unwrap();
|
||||
map.insert("y", pos.y).unwrap();
|
||||
let map = text_container
|
||||
.insert_container("size", ContainerType::Map)
|
||||
.unwrap()
|
||||
.into_map()
|
||||
.unwrap();
|
||||
map.insert("x", size.x).unwrap();
|
||||
map.insert("y", size.y).unwrap();
|
||||
|
||||
let len = self.id_to_obj.len();
|
||||
self.id_to_obj.insert(len, text_container.id());
|
||||
}
|
||||
DrawAction::CreateRect { pos, .. } => {
|
||||
let rect = self.rects.insert_container(0, ContainerType::Map).unwrap();
|
||||
let rect_map = rect.into_map().unwrap();
|
||||
let pos_map = rect_map
|
||||
.insert_container("pos", ContainerType::Map)
|
||||
.unwrap()
|
||||
.into_map()
|
||||
.unwrap();
|
||||
pos_map.insert("x", pos.x).unwrap();
|
||||
pos_map.insert("y", pos.y).unwrap();
|
||||
|
||||
let size_map = rect_map
|
||||
.insert_container("size", ContainerType::Map)
|
||||
.unwrap()
|
||||
.into_map()
|
||||
.unwrap();
|
||||
size_map.insert("width", pos.x).unwrap();
|
||||
size_map.insert("height", pos.y).unwrap();
|
||||
|
||||
let len = self.id_to_obj.len();
|
||||
self.id_to_obj.insert(len, rect_map.id());
|
||||
}
|
||||
DrawAction::Move { id, relative_to } => {
|
||||
let Some(id) = self.id_to_obj.get(&(*id as usize)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let map = self.doc.get_map(id);
|
||||
let pos_map = map.get("pos").unwrap().unwrap_right().into_map().unwrap();
|
||||
let x = pos_map.get("x").unwrap().unwrap_left().into_i32().unwrap();
|
||||
let y = pos_map.get("y").unwrap().unwrap_left().into_i32().unwrap();
|
||||
pos_map.insert("x", x + relative_to.x).unwrap();
|
||||
pos_map.insert("y", y + relative_to.y).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DrawActors {
|
||||
pub docs: Vec<DrawActor>,
|
||||
}
|
||||
|
||||
impl DrawActors {
|
||||
pub fn new(size: usize) -> Self {
|
||||
let docs = (0..size).map(|i| DrawActor::new(i as u64)).collect();
|
||||
Self { docs }
|
||||
}
|
||||
|
||||
pub fn apply_action(&mut self, action: &mut Action<DrawAction>) {
|
||||
match action {
|
||||
Action::Action { peer, action } => {
|
||||
self.docs[*peer].apply_action(action);
|
||||
}
|
||||
Action::Sync { from, to } => {
|
||||
let vv = self.docs[*from].doc.oplog_vv();
|
||||
let data = self.docs[*from].doc.export_from(&vv);
|
||||
self.docs[*to].doc.import(&data).unwrap();
|
||||
}
|
||||
Action::SyncAll => self.sync_all(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync_all(&mut self) {
|
||||
let (first, rest) = self.docs.split_at_mut(1);
|
||||
for doc in rest.iter_mut() {
|
||||
let vv = first[0].doc.oplog_vv();
|
||||
first[0].doc.import(&doc.doc.export_from(&vv)).unwrap();
|
||||
}
|
||||
for doc in rest.iter_mut() {
|
||||
let vv = doc.doc.oplog_vv();
|
||||
doc.doc.import(&first[0].doc.export_from(&vv)).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_async_draw_workflow(
|
||||
peer_num: usize,
|
||||
action_num: usize,
|
||||
actions_before_sync: usize,
|
||||
seed: u64,
|
||||
) -> (DrawActors, Instant) {
|
||||
let seed = create_seed(seed, action_num * 32);
|
||||
let mut actions =
|
||||
gen_async_actions::<DrawAction>(action_num, peer_num, &seed, actions_before_sync, |_| {})
|
||||
.unwrap();
|
||||
let mut actors = DrawActors::new(peer_num);
|
||||
let start = Instant::now();
|
||||
for action in actions.iter_mut() {
|
||||
actors.apply_action(action);
|
||||
}
|
||||
|
||||
(actors, start)
|
||||
}
|
||||
|
||||
pub fn run_realtime_collab_draw_workflow(
|
||||
peer_num: usize,
|
||||
action_num: usize,
|
||||
seed: u64,
|
||||
) -> (DrawActors, Instant) {
|
||||
let seed = create_seed(seed, action_num * 32);
|
||||
let mut actions =
|
||||
gen_realtime_actions::<DrawAction>(action_num, peer_num, &seed, |_| {}).unwrap();
|
||||
let mut actors = DrawActors::new(peer_num);
|
||||
let start = Instant::now();
|
||||
for action in actions.iter_mut() {
|
||||
actors.apply_action(action);
|
||||
}
|
||||
|
||||
(actors, start)
|
||||
}
|
2
crates/benches/src/lib.rs
Normal file
2
crates/benches/src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod draw;
|
||||
pub mod sheet;
|
25
crates/benches/src/sheet.rs
Normal file
25
crates/benches/src/sheet.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use loro::{LoroDoc, LoroList, LoroMap};
|
||||
|
||||
pub struct Actor {
|
||||
pub doc: LoroDoc,
|
||||
cols: LoroList,
|
||||
rows: LoroList,
|
||||
}
|
||||
|
||||
impl Actor {}
|
||||
|
||||
pub fn init_sheet() -> LoroDoc {
|
||||
let doc = LoroDoc::new();
|
||||
doc.set_peer_id(0).unwrap();
|
||||
let cols = doc.get_list("cols");
|
||||
let rows = doc.get_list("rows");
|
||||
for i in 0..bench_utils::sheet::SheetAction::MAX_ROW {
|
||||
rows.push_container(loro::ContainerType::Map).unwrap();
|
||||
}
|
||||
|
||||
for i in 0..bench_utils::sheet::SheetAction::MAX_COL {
|
||||
cols.push(i as i32).unwrap();
|
||||
}
|
||||
|
||||
doc
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
use bench_utils::draw::{gen_draw_actions, DrawAction};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use loro_internal::LoroDoc;
|
||||
|
||||
pub fn draw(c: &mut Criterion) {
|
||||
let mut data = None;
|
||||
c.bench_function("simulate drawing", |b| {
|
||||
if data.is_none() {
|
||||
data = Some(gen_draw_actions(100, 1000));
|
||||
}
|
||||
|
||||
let mut loro = LoroDoc::new();
|
||||
b.iter(|| {
|
||||
loro = LoroDoc::new();
|
||||
let _paths = loro.get_list("all_paths");
|
||||
let _texts = loro.get_list("all_texts");
|
||||
for action in data.as_ref().unwrap().iter() {
|
||||
match action {
|
||||
DrawAction::DrawPath { points: _, color: _ } => {}
|
||||
DrawAction::Text {
|
||||
id: _,
|
||||
text: _,
|
||||
pos: _,
|
||||
width: _,
|
||||
height: _,
|
||||
} => todo!(),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
println!("Snapshot size = {}", loro.export_snapshot().len())
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, draw);
|
||||
criterion_main!(benches);
|
|
@ -109,6 +109,12 @@ impl IntoContainerId for ContainerID {
|
|||
}
|
||||
}
|
||||
|
||||
impl IntoContainerId for &ContainerID {
|
||||
fn into_container_id(self, _arena: &SharedArena, _kind: ContainerType) -> ContainerID {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoContainerId for ContainerIdx {
|
||||
fn into_container_id(self, arena: &SharedArena, kind: ContainerType) -> ContainerID {
|
||||
assert_eq!(self.get_type(), kind);
|
||||
|
@ -116,6 +122,13 @@ impl IntoContainerId for ContainerIdx {
|
|||
}
|
||||
}
|
||||
|
||||
impl IntoContainerId for &ContainerIdx {
|
||||
fn into_container_id(self, arena: &SharedArena, kind: ContainerType) -> ContainerID {
|
||||
assert_eq!(self.get_type(), kind);
|
||||
arena.get_container_id(*self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ContainerIdRaw {
|
||||
fn from(value: String) -> Self {
|
||||
ContainerIdRaw::Root { name: value.into() }
|
||||
|
|
|
@ -669,6 +669,16 @@ impl LoroDoc {
|
|||
pub(crate) fn weak_state(&self) -> Weak<Mutex<DocState>> {
|
||||
Arc::downgrade(&self.state)
|
||||
}
|
||||
|
||||
pub fn len_ops(&self) -> usize {
|
||||
let oplog = self.oplog.lock().unwrap();
|
||||
oplog.vv().iter().map(|(_, ops)| *ops).sum::<i32>() as usize
|
||||
}
|
||||
|
||||
pub fn len_changes(&self) -> usize {
|
||||
let oplog = self.oplog.lock().unwrap();
|
||||
oplog.len_changes()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_encode_header(bytes: &[u8]) -> Result<(&[u8], EncodeMode), LoroError> {
|
||||
|
|
|
@ -186,6 +186,16 @@ impl LoroDoc {
|
|||
self.doc.state_vv()
|
||||
}
|
||||
|
||||
/// Get the total number of operations in the `OpLog`
|
||||
pub fn len_ops(&self) -> usize {
|
||||
self.doc.len_ops()
|
||||
}
|
||||
|
||||
/// Get the total number of changes in the `OpLog`
|
||||
pub fn len_changes(&self) -> usize {
|
||||
self.doc.len_changes()
|
||||
}
|
||||
|
||||
pub fn get_deep_value(&self) -> LoroValue {
|
||||
self.doc.get_deep_value()
|
||||
}
|
||||
|
@ -320,8 +330,14 @@ impl LoroList {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push(&self, v: LoroValue) -> LoroResult<()> {
|
||||
self.handler.push(v)
|
||||
pub fn push(&self, v: impl Into<LoroValue>) -> LoroResult<()> {
|
||||
self.handler.push(v.into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push_container(&self, c_type: ContainerType) -> LoroResult<Container> {
|
||||
let pos = self.handler.len();
|
||||
Ok(Container::from(self.handler.insert_container(pos, c_type)?))
|
||||
}
|
||||
|
||||
pub fn for_each<I>(&self, f: I)
|
||||
|
|
Loading…
Reference in a new issue