Skip to content

Commit

Permalink
Various UX and perf improvements. Additional benchmarks (#7)
Browse files Browse the repository at this point in the history
* Remove page_size dependency and use default bucket size. Fix multi-bucket bug
* Forbid unsafe code
* Implement From trait in Size
* Change RapIdArena to be threadsafe
* Change RapId to struct
* Small refactor of a test
* Add iterator for RapIdArena and tests
* Add a workflow that can be use to cache BuildIt builds
* Remove coverage badge
* Add some alt keys and change colors for better UX on Macs
* Add additional DirectoryItem::build() benchmark with more threads
* Minor update to readme for Mac/Linux.
* Add additional benchmarks to test different thread counts
* Add a few test cases for alt keys
  • Loading branch information
emilevr authored Nov 6, 2023
1 parent fb55f83 commit 4589bff
Show file tree
Hide file tree
Showing 22 changed files with 655 additions and 274 deletions.
Empty file modified .git-hooks/post-checkout
100644 → 100755
Empty file.
Empty file modified .git-hooks/pre-commit
100644 → 100755
Empty file.
2 changes: 1 addition & 1 deletion .github/workflows/benchmark.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ jobs:
restore-keys: tmp.sample-${{ github.job }}

- name: Run all benchmarks
run: buildit benchmark
run: buildit benchmark --bench-names directory_item_build directory_item_build_x2 directory_item_build_x3
11 changes: 0 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 13 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,25 @@ path = "src/space_bench.rs"
required-features = ["bench"]

[[bench]]
name = "arenas"
name = "arenas_alloc"
harness = false

[[bench]]
name = "arenas_read"
harness = false

[[bench]]
name = "directory_item_build"
harness = false

[[bench]]
name = "directory_item_build_x2"
harness = false

[[bench]]
name = "directory_item_build_x3"
harness = false

[features]
# Uncomment the "default = ..." below to include the nightly features in the build by default, and in the Rust
# analyzer. If you do so then you will have to add "+nightly" to cargo commands, e.g. "cargo +nightly build".
Expand All @@ -52,7 +64,6 @@ crossterm = { version = "^0.27.0", optional = true }
dirs = { version = "^5.0.1", optional = true }
log = { version = "^0.4.20", optional = true }
log4rs = { version = "^1.2.0", optional = true }
page_size = "^0.6.0"
ratatui = { version = "^0.23.0", default-features = false, features = ["crossterm"], optional = true }
rayon = "^1.7.0"
serde = { version = "^1.0.188", features = ["derive"], optional = true }
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,12 @@ This repository contains:
### Enabling Git hooks (once off)

After the initial checkout please run the post-checkout hook manually via:
```git config core.hooksPath ./.git-hooks ; git hook run post-checkout```
```bash
git config core.hooksPath ./.git-hooks
# On MacOS / Linux, make the hook scripts executable. On Windows, comment out the line below:
chmod +x .git-hooks/*
git hook run post-checkout
```

This will setup the hooks directory, enabling the pre-commit hook to run linting, code coverage and benchmarks.

Expand Down
13 changes: 7 additions & 6 deletions benches/arenas.rs → benches/arenas_alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use space_rs::{
rapid_arena::{RapId, RapIdArena},
DirectoryItemType, Size, SizeDisplayFormat,
};
use std::time::Duration;
use std::{ops::DerefMut, time::Duration};

pub struct StdItem {
pub path_segment: String,
Expand Down Expand Up @@ -42,9 +42,9 @@ pub struct RapIdArenaItem {
pub children: Vec<RapId<RapIdArenaItem>>,
}

fn bench_arenas(c: &mut Criterion) {
fn bench_arenas_alloc(c: &mut Criterion) {
let sample_size = 100;
let mut group = c.benchmark_group("arenas");
let mut group = c.benchmark_group("arenas_alloc");
group.measurement_time(Duration::from_secs(3));
group.warm_up_time(Duration::from_secs(1));
group.sample_size(sample_size);
Expand Down Expand Up @@ -243,7 +243,7 @@ fn benchmark_rapid_arena(

b.iter(|| {
black_box({
let parent = arena.alloc(RapIdArenaItem {
let mut parent = arena.alloc(RapIdArenaItem {
parent: None,
path_segment: "parent".to_string(),
item_type: DirectoryItemType::Directory,
Expand All @@ -261,7 +261,7 @@ fn benchmark_rapid_arena(
children: vec![],
});

arena.get_mut(parent).unwrap().children.push(child);
parent.deref_mut().children.push(child);
})
});

Expand Down Expand Up @@ -327,7 +327,8 @@ fn report_memory_usage(
} else {
println!("Couldn't get the current memory usage!");
}
println!("");
}

criterion_group!(benches, bench_arenas);
criterion_group!(benches, bench_arenas_alloc);
criterion_main!(benches);
171 changes: 171 additions & 0 deletions benches/arenas_read.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
use criterion::{criterion_group, criterion_main, Criterion};
use id_arena::{Arena, Id};
use space_rs::{
rapid_arena::{RapId, RapIdArena},
DirectoryItemType, Size,
};
use std::{
ops::{Deref, DerefMut},
time::Duration,
};

const ITEM_COUNT: usize = 100000;
const ITEM_READ_COUNT_PER_ITERATION: usize = 5;

pub struct StdItem {
pub path_segment: String,
pub item_type: DirectoryItemType,
pub size_in_bytes: Size,
pub child_count: usize,
pub children: Vec<StdItem>,
}

pub struct BumpaloItem<'a> {
pub path_segment: &'a str,
pub item_type: DirectoryItemType,
pub size_in_bytes: &'a Size,
pub child_count: usize,
pub children: Vec<BumpaloItem<'a>>,
}

pub struct IdArenaItem {
pub parent: Option<Id<IdArenaItem>>,
pub path_segment: String,
pub item_type: DirectoryItemType,
pub size_in_bytes: Size,
pub child_count: usize,
pub children: Vec<Id<IdArenaItem>>,
}

pub struct RapIdArenaItem {
pub parent: Option<RapId<RapIdArenaItem>>,
pub path_segment: String,
pub item_type: DirectoryItemType,
pub size_in_bytes: Size,
pub child_count: usize,
pub children: Vec<RapId<RapIdArenaItem>>,
}

fn bench_arenas_read(c: &mut Criterion) {
let mut group = c.benchmark_group("arenas_read");
group.measurement_time(Duration::from_secs(3));
group.warm_up_time(Duration::from_secs(1));
group.sample_size(100);

benchmark_std(&mut group);
benchmark_id_arena(&mut group);
benchmark_rapid(&mut group);

group.finish();
}

fn benchmark_std(group: &mut criterion::BenchmarkGroup<'_, criterion::measurement::WallTime>) {
let mut items = Vec::<StdItem>::with_capacity(ITEM_COUNT);
// Alloc
for _ in 0..ITEM_COUNT {
let mut parent = StdItem {
path_segment: "parent".into(),
item_type: DirectoryItemType::Directory,
size_in_bytes: Size::new(1234),
child_count: 1,
children: vec![],
};

parent.children.push(StdItem {
path_segment: "child".into(),
item_type: DirectoryItemType::File,
size_in_bytes: Size::new(1234),
child_count: 0,
children: vec![],
});

items.push(parent);
}

group.bench_function("std", |b| {
b.iter(|| {
for i in 0..ITEM_COUNT * ITEM_READ_COUNT_PER_ITERATION {
let _ = &items[i % ITEM_COUNT];
}
});
});
}

fn benchmark_id_arena(group: &mut criterion::BenchmarkGroup<'_, criterion::measurement::WallTime>) {
let mut arena = Arena::<IdArenaItem>::new();
let mut ids = vec![];

// Alloc
for _ in 0..ITEM_COUNT {
let parent = arena.alloc(IdArenaItem {
parent: None,
path_segment: "parent".to_string(),
item_type: DirectoryItemType::Directory,
size_in_bytes: Size::new(1234),
child_count: 1,
children: vec![],
});

let child = arena.alloc(IdArenaItem {
parent: Some(parent),
path_segment: "child".to_string(),
item_type: DirectoryItemType::File,
size_in_bytes: Size::new(1234),
child_count: 0,
children: vec![],
});

arena.get_mut(parent).unwrap().children.push(child);

ids.push(parent);
}

group.bench_function("id-arena", |b| {
b.iter(|| {
for i in 0..ITEM_COUNT * ITEM_READ_COUNT_PER_ITERATION {
let _ = &arena.get(ids[i % ITEM_COUNT]);
}
});
});
}

fn benchmark_rapid(group: &mut criterion::BenchmarkGroup<'_, criterion::measurement::WallTime>) {
let mut arena = RapIdArena::<RapIdArenaItem>::new();
let mut ids = vec![];

// Alloc
for _ in 0..ITEM_COUNT {
let mut parent = arena.alloc(RapIdArenaItem {
parent: None,
path_segment: "parent".to_string(),
item_type: DirectoryItemType::Directory,
size_in_bytes: Size::new(1234),
child_count: 1,
children: vec![],
});

let child = arena.alloc(RapIdArenaItem {
parent: Some(parent),
path_segment: "child".to_string(),
item_type: DirectoryItemType::File,
size_in_bytes: Size::new(1234),
child_count: 0,
children: vec![],
});

parent.deref_mut().children.push(child);

ids.push(parent);
}

group.bench_function("rapid-arena", |b| {
b.iter(|| {
for i in 0..ITEM_COUNT * ITEM_READ_COUNT_PER_ITERATION {
let _ = ids[i % ITEM_COUNT].deref();
}
});
});
}

criterion_group!(benches, bench_arenas_read);
criterion_main!(benches);
34 changes: 34 additions & 0 deletions benches/directory_item_build_x2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use space_rs::DirectoryItem;
use std::path::Path;

const DIRECTORY_PATH: &str = "./tmp.sample";

pub fn directory_item_build_x2(c: &mut Criterion) {
let path = &Path::new(DIRECTORY_PATH).to_path_buf();

use std::thread::available_parallelism;
let thread_count = available_parallelism().unwrap().get() * 2;
println!("Using {} Rayon threads", thread_count);

rayon::ThreadPoolBuilder::new()
.num_threads(thread_count)
.build_global()
.unwrap();

c.bench_function(
&format!("DirectoryItem::build() x2 on {}", path.display()),
|b| {
b.iter(|| {
black_box(DirectoryItem::build(vec![path.clone()]));
})
},
);
}

criterion_group! {
name = benches;
config = Criterion::default().sample_size(10);
targets = directory_item_build_x2
}
criterion_main!(benches);
34 changes: 34 additions & 0 deletions benches/directory_item_build_x3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use space_rs::DirectoryItem;
use std::path::Path;

const DIRECTORY_PATH: &str = "./tmp.sample";

pub fn directory_item_build_x3(c: &mut Criterion) {
let path = &Path::new(DIRECTORY_PATH).to_path_buf();

use std::thread::available_parallelism;
let thread_count = available_parallelism().unwrap().get() * 3;
println!("Using {} Rayon threads", thread_count);

rayon::ThreadPoolBuilder::new()
.num_threads(thread_count)
.build_global()
.unwrap();

c.bench_function(
&format!("DirectoryItem::build() x3 on {}", path.display()),
|b| {
b.iter(|| {
black_box(DirectoryItem::build(vec![path.clone()]));
})
},
);
}

criterion_group! {
name = benches;
config = Criterion::default().sample_size(10);
targets = directory_item_build_x3
}
criterion_main!(benches);
Loading

0 comments on commit 4589bff

Please sign in to comment.