mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-29 04:47:10 -06:00
A paragraph with keep with next measures the paragraph after it before it is placed (next_first_line). That measurement called layout_para directly, so every paragraph following a heading was laid out twice in each layout pass: once to measure it and once, through the cache, when it was placed. The measurement now goes through the layout cache under the key para_labelled uses (Ctx::para_ahead) when its inputs are the same: no proofing, no typed word, not inside a text box, no inline text boxes that resize, the body's tracked-deletion view. Otherwise it is laid out on its own as before. It counts nothing (list labels, equation numbers and the page-total flag are restored), so pagination is unchanged. Adds crates/engine/examples/export_bench.rs, which saves a long generated document (the sample document 200 times, 259 pages) through io::save_bytes and prints wall times: cargo run --release -p wordcraft-engine --example export_bench -- pdf 200 9 PDF export on that document, interleaved with main (Ryzen, 16 threads): median 286 ms -> 256 ms (-10.6 %). The PDF bytes are identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
2.5 KiB
Rust
65 lines
2.5 KiB
Rust
//! Export timing: saves a long generated document (the built-in sample document repeated) through
|
|
//! the real save path, the way `file.save` / `file.export` write it, and prints wall times.
|
|
//!
|
|
//! `cargo run --release -p wordcraft-engine --example export_bench -- [ext] [copies] [runs] [out]`
|
|
//!
|
|
//! Defaults: `pdf`, 40 copies (about 100 pages), 7 runs. With `out` the last run's bytes are
|
|
//! written there, to compare the output of two builds.
|
|
|
|
use std::sync::Arc;
|
|
use std::time::Instant;
|
|
|
|
use wordcraft_engine::doc::props::CharProps;
|
|
use wordcraft_engine::doc::{Block, Document};
|
|
use wordcraft_engine::{io, sample};
|
|
|
|
/// The sample document's body `copies` times, each paragraph ending in its copy number so no two
|
|
/// copies share text (as the chapters of a real long document don't).
|
|
fn long_document(copies: usize) -> Document {
|
|
let mut d = sample::sample_document();
|
|
// A fixed creation date: the PDF's dates (and the ID derived from them) stay the same from run to run.
|
|
d.core.created = "2026-01-01T00:00:00Z".into();
|
|
let one: Vec<Block> = d.body.iter().map(|b| (**b).clone()).collect();
|
|
d.body.clear();
|
|
for i in 0..copies {
|
|
for b in &one {
|
|
let mut b = b.clone();
|
|
if let Block::Para(p) = &mut b {
|
|
let at = p.len();
|
|
let _ = p.insert_text(at, &format!(" {i}"), &CharProps::default());
|
|
}
|
|
d.body.push(Arc::new(b));
|
|
}
|
|
}
|
|
d
|
|
}
|
|
|
|
fn main() -> Result<(), String> {
|
|
let args: Vec<String> = std::env::args().skip(1).collect();
|
|
let ext = args.first().map_or("pdf", String::as_str);
|
|
let copies = args.get(1).and_then(|s| s.parse().ok()).unwrap_or(40);
|
|
let runs = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(7usize).max(1);
|
|
let doc = long_document(copies);
|
|
let name = format!("bench.{ext}");
|
|
let mut ms = Vec::with_capacity(runs);
|
|
let mut bytes = Vec::new();
|
|
for _ in 0..runs {
|
|
let t = Instant::now();
|
|
bytes = io::save_bytes(&name, &doc)?;
|
|
ms.push(t.elapsed().as_secs_f64() * 1000.0);
|
|
}
|
|
let mut sorted = ms.clone();
|
|
sorted.sort_by(f64::total_cmp);
|
|
let median = sorted.get(sorted.len() / 2).copied().unwrap_or_default();
|
|
let min = sorted.first().copied().unwrap_or_default();
|
|
println!(
|
|
"{ext}: {copies} copies, {} bytes, runs {:?} ms, median {median:.1} ms, min {min:.1} ms",
|
|
bytes.len(),
|
|
ms.iter().map(|m| m.round()).collect::<Vec<_>>()
|
|
);
|
|
if let Some(out) = args.get(3) {
|
|
std::fs::write(out, &bytes).map_err(|e| e.to_string())?;
|
|
}
|
|
Ok(())
|
|
}
|