perf(render): pre-size generated view output
This commit is contained in:
@@ -6,6 +6,10 @@ edition.workspace = true
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "render"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
hemplate = { path = "../../hemplate/hemplate" }
|
||||
hemx-core = { path = "../hemx-core" }
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
//! Generated-view rendering baseline: `cargo bench -p hemx --bench render`.
|
||||
|
||||
use hemplate::Hemplate;
|
||||
use std::hint::black_box;
|
||||
use std::time::Instant;
|
||||
|
||||
const ITERATIONS: u32 = 100_000;
|
||||
const ROUNDS: usize = 7;
|
||||
|
||||
#[derive(Hemplate)]
|
||||
struct BenchPage {
|
||||
title: String,
|
||||
body: String,
|
||||
items: Vec<String>,
|
||||
}
|
||||
|
||||
fn page() -> BenchPage {
|
||||
BenchPage {
|
||||
title: String::from("Production readiness"),
|
||||
body: "Checked hypermedia without a parallel client application. ".repeat(4),
|
||||
items: (0..20).map(|index| format!("item-{index}")).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn median_ns(mut render: impl FnMut() -> usize) -> u128 {
|
||||
for _ in 0..10_000 {
|
||||
black_box(render());
|
||||
}
|
||||
let mut rounds = [0_u128; ROUNDS];
|
||||
for round in &mut rounds {
|
||||
let start = Instant::now();
|
||||
for _ in 0..ITERATIONS {
|
||||
black_box(render());
|
||||
}
|
||||
*round = start.elapsed().as_nanos() / u128::from(ITERATIONS);
|
||||
}
|
||||
rounds.sort_unstable();
|
||||
rounds[ROUNDS / 2]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let view = page();
|
||||
let unhinted = median_ns(|| {
|
||||
let mut html = String::new();
|
||||
view.render_into(&mut html).expect("generated view renders");
|
||||
black_box(html.len())
|
||||
});
|
||||
let hinted = median_ns(|| {
|
||||
let mut html = String::with_capacity(view.size_hint());
|
||||
view.render_into(&mut html).expect("generated view renders");
|
||||
black_box(html.len())
|
||||
});
|
||||
println!(
|
||||
"unhinted_median_ns={unhinted} hinted_median_ns={hinted} size_hint={} html_len={}",
|
||||
view.size_hint(),
|
||||
view.render().expect("generated view renders").len(),
|
||||
);
|
||||
}
|
||||
+26
-1
@@ -80,7 +80,7 @@ pub mod __private {
|
||||
}
|
||||
|
||||
fn render_template(view: &impl hemplate::Hemplate) -> Html {
|
||||
let mut html = String::new();
|
||||
let mut html = String::with_capacity(view.size_hint());
|
||||
view.render_into(&mut html)
|
||||
.expect("hemplate view renders into hemx effect payload");
|
||||
__private::html_trusted(html)
|
||||
@@ -186,6 +186,8 @@ pub mod prelude {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::cell::Cell;
|
||||
|
||||
struct InlineView;
|
||||
|
||||
impl hemplate::Hemplate for InlineView {
|
||||
@@ -195,6 +197,29 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
struct HintTrackingView(Cell<bool>);
|
||||
|
||||
impl hemplate::Hemplate for HintTrackingView {
|
||||
fn render_into(&self, out: &mut String) -> Result<(), hemplate::error::HemplateError> {
|
||||
out.push_str("<strong>hinted</strong>");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> usize {
|
||||
self.0.set(true);
|
||||
23
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trusted_render_path_uses_the_view_size_hint() {
|
||||
// req: view/003 req: html_safety/002
|
||||
let view = HintTrackingView(Cell::new(false));
|
||||
|
||||
assert_eq!(crate::page(&view).as_str(), "<strong>hinted</strong>");
|
||||
assert!(view.0.get(), "trusted render path must consult size_hint");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_is_the_short_safe_html_helper() {
|
||||
// req: dx/006 req: html_safety/002
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>{+ self.title +}</title></head>
|
||||
<body>
|
||||
<h1>{+ self.title +}</h1>
|
||||
<p>{+ self.body +}</p>
|
||||
<ul>
|
||||
<li h-for="item in &self.items">{+ item +}</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user