Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
364 changes: 327 additions & 37 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,16 @@ windows = { version = "0.60.0", features = ["Win32_System_Console", "Win32_Syste
embed-resource = "3.0.6"

[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
pretty_assertions = "1.4.1"
proptest = "1.6.0"
tempfile = "3.14.0"
velcro = "0.5.4"

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

[profile.dev]
opt-level = 1

Expand Down
165 changes: 165 additions & 0 deletions benches/semantic_scan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};

use ludusavi::path::StrictPath;
use ludusavi::resource::manifest::Store;
use ludusavi::scan::saves::ScanOrigin;
use ludusavi::semantic::SemanticPath;
use ludusavi::semantic::convert::{
KnownFolders, derive_from_manifest_origin, windows_physical_to_semantic, wine_physical_to_semantic,
};

fn make_known_folders() -> KnownFolders {
KnownFolders {
saved_games: Some("C:/Users/Alice/Saved Games".to_string()),
documents: Some("C:/Users/Alice/Documents".to_string()),
local_app_data: Some("C:/Users/Alice/AppData/Local".to_string()),
app_data: Some("C:/Users/Alice/AppData/Roaming".to_string()),
public: Some("C:/Users/Public".to_string()),
program_data: Some("C:/ProgramData".to_string()),
windows: Some("C:/Windows".to_string()),
user_profile: Some("C:/Users/Alice".to_string()),
}
}

fn bench_parse(c: &mut Criterion) {
c.bench_function("semantic_parse", |b| {
b.iter(|| {
SemanticPath::parse("<winDocuments>/Game/save.dat").unwrap();
})
});
}

fn bench_serialize(c: &mut Criterion) {
let sp = SemanticPath::parse("<winDocuments>/Game/save.dat").unwrap();
c.bench_function("semantic_serialize", |b| {
b.iter(|| {
sp.serialize();
})
});
}

fn bench_storage_path(c: &mut Criterion) {
let sp = SemanticPath::parse("<winDocuments>/Game/save.dat").unwrap();
c.bench_function("semantic_storage_path", |b| {
b.iter(|| {
sp.storage_path();
})
});
}

fn bench_windows_to_semantic(c: &mut Criterion) {
let kf = make_known_folders();
let paths = [
StrictPath::new("C:/Users/Alice/Documents/Game/save.dat"),
StrictPath::new("C:/Users/Alice/AppData/Roaming/Game/config.ini"),
StrictPath::new("C:/Users/Alice/AppData/Local/Game/cache.dat"),
StrictPath::new("C:/ProgramData/Game/telemetry.dat"),
StrictPath::new("D:/Games/save.dat"),
];

let mut group = c.benchmark_group("windows_physical_to_semantic");
for (i, path) in paths.iter().enumerate() {
group.bench_with_input(BenchmarkId::new("path", i), path, |b, p| {
b.iter(|| {
windows_physical_to_semantic(p, &kf);
})
});
}
group.finish();
}

fn bench_wine_to_semantic(c: &mut Criterion) {
let prefix = StrictPath::new("/home/deck/Prefixes/Game");
let paths = [
StrictPath::new("/home/deck/Prefixes/Game/drive_c/users/steamuser/Documents/Game/save.dat"),
StrictPath::new("/home/deck/Prefixes/Game/drive_c/users/steamuser/AppData/Roaming/Game/config.ini"),
StrictPath::new("/home/deck/Prefixes/Game/drive_c/ProgramData/Game/telemetry.dat"),
StrictPath::new("/home/deck/Prefixes/Game/drive_d/Games/save.dat"),
];

let mut group = c.benchmark_group("wine_physical_to_semantic");
for (i, path) in paths.iter().enumerate() {
group.bench_with_input(BenchmarkId::new("path", i), path, |b, p| {
b.iter(|| {
wine_physical_to_semantic(p, &prefix, "steamuser");
})
});
}
group.finish();
}

fn bench_manifest_derive(c: &mut Criterion) {
let origins = [
ScanOrigin {
manifest_path: "<winDocuments>/Remedy/Alan Wake".to_string(),
store: Store::Other,
expanded_prefix: "C:/Users/Alice/Documents".to_string(),
matched_prefix_len: 25,
tail: "Remedy/Alan Wake/save.dat".to_string(),
},
ScanOrigin {
manifest_path: "<root>/userdata/<storeUserId>/<storeGameId>/remote".to_string(),
store: Store::Steam,
expanded_prefix: "C:/Program Files (x86)/Steam".to_string(),
matched_prefix_len: 34,
tail: "userdata/12345/67890/remote/save.dat".to_string(),
},
];

let mut group = c.benchmark_group("manifest_derive");
for (i, origin) in origins.iter().enumerate() {
group.bench_with_input(BenchmarkId::new("origin", i), origin, |b, o| {
b.iter(|| {
derive_from_manifest_origin(o);
})
});
}
group.finish();
}

fn bench_batch_scan_simulation(c: &mut Criterion) {
let kf = make_known_folders();
let prefix = StrictPath::new("/home/deck/Prefixes/Game");

// Simulate scanning 500 games worth of paths
let windows_paths: Vec<StrictPath> = (0..500)
.map(|i| StrictPath::new(format!("C:/Users/Alice/Documents/Game{}/save.dat", i)))
.collect();

let wine_paths: Vec<StrictPath> = (0..500)
.map(|i| {
StrictPath::new(format!(
"/home/deck/Prefixes/Game/drive_c/users/steamuser/Documents/Game{}/save.dat",
i
))
})
.collect();

c.bench_function("batch_500_windows", |b| {
b.iter(|| {
for path in &windows_paths {
windows_physical_to_semantic(path, &kf);
}
})
});

c.bench_function("batch_500_wine", |b| {
b.iter(|| {
for path in &wine_paths {
wine_physical_to_semantic(path, &prefix, "steamuser");
}
})
});
}

criterion_group!(
benches,
bench_parse,
bench_serialize,
bench_storage_path,
bench_windows_to_semantic,
bench_wine_to_semantic,
bench_manifest_derive,
bench_batch_scan_simulation,
);
criterion_main!(benches);
Loading