Files
vectorcraft/crates/engine/examples/export_bench.rs
T
NetterSilverandClaude Opus 5.5 19d4adb39c Make SVG export def ids linear instead of quadratic
Writer::fresh_id probed prefix-1, prefix-2, ... from 1 on every call,
formatting and hashing each candidate, so writing N gradients, clip
paths or masks cost O(N^2). Ids are never released, so every number
below the last one handed out for a prefix is taken: remember where
the search for each prefix stopped and resume there. The ids written
are the same as before.

examples/still-life.vectorcraft (12k gradients, 6.3 MB SVG) exported
in about 8.6 s (median); it now takes about 0.15 s. Output is byte-identical
for every document in examples/.

Add crates/engine/examples/export_bench.rs to time document.export
on the example documents, and a test pinning the generated id
sequence when object names already take some of the numbers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 11:52:22 +01:00

48 lines
2.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! `cargo run --release -p vectorcraft-engine --example export_bench -- [FORMAT] [FILE…]` — wall
//! time of `document.export` (default SVG) for each file (default: the documents in `examples/`).
//! `ITERS` sets the runs per file (default 7); prints the median and the fastest run.
// Example (dev tool): unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::path::PathBuf;
use std::time::Instant;
use serde_json::json;
use vectorcraft_engine::Session;
fn main() {
let mut args = std::env::args().skip(1);
let format = args.next().unwrap_or_else(|| "svg".into());
let mut files: Vec<PathBuf> = args.map(PathBuf::from).collect();
if files.is_empty() {
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../examples");
files = std::fs::read_dir(&dir).unwrap().map(|e| e.unwrap().path()).filter(|p| p.extension().is_some_and(|e| e == "vectorcraft")).collect();
files.sort();
}
let iters: usize = std::env::var("ITERS").ok().and_then(|v| v.parse().ok()).unwrap_or(7).max(1);
let out_dir = std::env::temp_dir().join(format!("vectorcraft-export-bench-{}", std::process::id()));
std::fs::create_dir_all(&out_dir).unwrap();
let mut total = 0.0;
for file in &files {
let mut s = Session::new();
s.execute("document.open", &json!({ "path": file.to_string_lossy() })).unwrap();
let name = file.file_stem().unwrap().to_string_lossy().into_owned();
let out = out_dir.join(format!("{name}.{format}"));
let p = json!({ "path": out.to_string_lossy(), "format": format });
let mut ms: Vec<f64> = (0..iters)
.map(|_| {
let t = Instant::now();
s.execute("document.export", &p).unwrap();
t.elapsed().as_secs_f64() * 1000.0
})
.collect();
ms.sort_by(f64::total_cmp);
let median = ms[ms.len() / 2];
total += median;
let bytes = std::fs::metadata(&out).map(|m| m.len()).unwrap_or(0);
println!("{name}.{format}: median {median:.1} ms, min {:.1} ms ({bytes} bytes)", ms[0]);
}
println!("total of medians: {total:.1} ms");
std::fs::remove_dir_all(&out_dir).ok();
}