Files
wordcraft/crates/engine/examples/export_bench.rs
T
NetterSilverandClaude Opus 5.5 8cdc172171 Lay out the paragraph after a kept heading once
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>
2026-10-27 13:39:09 +01:00

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(())
}