Add Tera benchmarks, and list SSR benchmark results in README

This commit is contained in:
Greg Johnston
2022-10-23 20:57:50 -04:00
parent 1d48da937b
commit 7dbd259b3f
7 changed files with 250 additions and 9 deletions

View File

@@ -72,12 +72,34 @@ Most of the examples assume youre using `nightly` Rust. If youre on stable
[`counters-stable` example](https://github.com/gbj/leptos/blob/main/examples/counters-stable/src/main.rs)
for examples of the correct API.
## Benchmarks
### Server-Side Rendering
Ive created a benchmark comparing Leptoss HTML rendering on the server to [Tera](https://github.com/Keats/tera), [Yew](https://github.com/yewstack/yew), and [Sycamore](https://github.com/sycamore-rs/sycamore). You can find the benchmark [here](https://github.com/gbj/leptos/tree/main/benchmarks) and run it yourself using `cargo bench`.
`cargo bench` ns/iter
<table>
<thead>
<tr><td></td><td>Tera</td><td>Leptos</td><td>Yew</td><td>Sycamore</td></tr>
</thead>
<tbody>
<tr><td>3 Counters</td><td align="right">3,454</td><td align="right">5,666</td><td align="right">34,984</td><td align="right">32,412</td></tr>
<tr><td>TodoMVC (no todos)</td><td align="right">2,396</td><td align="right">5,561</td><td align="right">38,725</td><td align="right">68,749</td></tr>
<tr><td>TodoMVC (1000 todos)</td><td align="right">3,829,447</td><td align="right">3,077,907</td><td align="right">5,125,639</td><td align="right">19,448,900</td></tr>
<tr><td><em>Average</em></td><td align="right">1.08</td><td align="right">1.65</td><td align="right">6.25</td><td align="right">9.36</td></tr>
</tbody>
</table>
As you can see, Leptos renders HTML roughly as fast as Tera, and scales well as templates become larger. It's significantly faster than the server-side HTML rendering done by similar frameworks.
### Client-Side Rendering
The gold standard for testing raw rendering performance for front-end web frameworks is the [js-framework-benchmark](https://github.com/krausest/js-framework-benchmark). The current results (which you can see if you check out `master` from the benchmark repo and open the results page) have Leptos as the fastest Rust/Wasm framework on this benchmark.
## FAQs
### Is it fast?
The gold standard for testing raw rendering performance for front-end web frameworks is the [js-framework-benchmark](https://github.com/krausest/js-framework-benchmark). I'm waiting for the next round of official results before making claims about performance here, but the unofficial results (which you can see if you check out `master` from the benchmark repo and open the results page) have Leptos as the fastest Rust/Wasm framework, on this benchmark.
### How is this different from Yew/Dioxus?
On the surface level, these libraries may seem similar. Yew is, of course, the most mature Rust library for web UI development and has a huge ecosystem. Dioxus is similar in many ways, being heavily inspired by React. Here are some conceptual differences between Leptos and these frameworks:

View File

@@ -12,11 +12,13 @@ miniserde = "0.1"
gloo = "0.8"
uuid = { version = "1", features = ["serde", "v4", "wasm-bindgen"] }
wasm-bindgen = "0.2"
lazy_static = "1"
log = "0.4"
strum = "0.24"
strum_macros = "0.24"
serde = { version = "1", features = ["derive", "rc"]}
serde_json = "1"
tera = "1"
[dependencies.web-sys]
version = "0.3"

View File

@@ -2,6 +2,6 @@
extern crate test;
mod reactive;
//mod reactive;
mod ssr;
mod todomvc;

View File

@@ -38,6 +38,48 @@ fn leptos_ssr_bench(b: &mut Bencher) {
});
}
#[bench]
fn tera_ssr_bench(b: &mut Bencher) {
use tera::*;
use serde::{Serialize, Deserialize};
static TEMPLATE: &str = r#"<main>
<h1>Welcome to our benchmark page.</h1>
<p>Here's some introductory text.</p>
{% for counter in counters %}
<div>
<button>-1</button>
<span>Value: {{ counter.value }}!</span>
<button>+1</button>
</div>
{% endfor %}
</main>"#;
lazy_static::lazy_static! {
static ref TERA: Tera = {
let mut tera = Tera::default();
tera.add_raw_templates(vec![("template.html", TEMPLATE)]).unwrap();
tera
};
}
#[derive(Serialize, Deserialize)]
struct Counter {
value: i32
}
b.iter(|| {
let mut ctx = Context::new();
ctx.insert("counters", &vec![
Counter { value: 0 },
Counter { value: 1},
Counter { value: 2 }
]);
let _ = TERA.render("template.html", &ctx).unwrap();
});
}
#[bench]
fn sycamore_ssr_bench(b: &mut Bencher) {
use sycamore::*;

View File

@@ -246,10 +246,7 @@ pub fn Todo(cx: Scope, todo: Todo) -> Element {
class="toggle"
type="checkbox"
prop:checked={move || (todo.completed)()}
on:input={move |ev| {
let checked = event_target_checked(&ev);
(todo.set_completed)(checked);
}}
/>
<label on:dblclick=move |_| set_editing(true)>
{move || todo.title.get()}

View File

@@ -2,6 +2,7 @@ use test::Bencher;
mod leptos;
mod sycamore;
mod tera;
mod yew;
#[bench]

View File

@@ -0,0 +1,177 @@
use test::Bencher;
static TEMPLATE: &str = r#"<main>
<section class="todoapp">
<header class="header">
<h1>"todos"</h1>
<input class="new-todo" placeholder="What needs to be done? />
</header>
<section class="main" class={{ main_class }}>
<input id="toggle-all" class="toggle-all" type="checkbox"
checked={{ toggle_checked }}
/>
<label for="toggle-all">"Mark all as complete"</label>
<ul class="todo-list">
{% for todo in todos %}
<li
class={{ todo.class }}
>
<div class="view">
<input
class="toggle"
type="checkbox"
checked={{ todo.completed }}
/>
<label>
{{ todo.label }}
</label>
<button class="destroy"/>
</div>
{% if todo.editing %}
<input
class="edit"
value={{ todo.label }}
/>
{% endif %}
</li>
{% endfor %}
</ul>
</section>
{% if todos_empty %}
{% else %}
<footer class="footer">
<span class="todo-count">
<strong>{{ todos_remaining }}</strong>
{% if todos_remaining == 1 %}
item
{% else %}
items
{% endif %}
left
</span>
<ul class="filters">
{% if mode_all %}
<li><a href="/" class="selected">All</a></li>
{% else %}
<li><a href="/">All</a></li>
{% endif %}
{% if mode_active %}
<li><a href="/active" class="selected">Active</a></li>
{% else %}
<li><a href="/active">Active</a></li>
{% endif %}
{% if mode_completed %}
<li><a href="/completed" class="selected">Completed</a></li>
{% else %}
<li><a href="/completed">Completed</a></li>
{% endif %}
</ul>
{% if todos_completed > 0 %}
<button
class="clear-completed hidden"
>
Clear completed
</button>
{% endif %}
</footer>
{% endif %}
</section>
<footer class="info">
<p>"Double-click to edit a todo"</p>
<p>"Created by "<a href="http://todomvc.com">"Greg Johnston"</a></p>
<p>"Part of "<a href="http://todomvc.com">"TodoMVC"</a></p>
</footer>
</main>"#;
#[bench]
fn tera_todomvc(b: &mut Bencher) {
use serde::{Deserialize, Serialize};
use tera::*;
lazy_static::lazy_static! {
static ref TERA: Tera = {
let mut tera = Tera::default();
tera.add_raw_templates(vec![("template.html", TEMPLATE)]).unwrap();
tera
};
}
#[derive(Serialize, Deserialize)]
struct Todo {
label: String,
completed: bool,
editing: bool,
class: String,
}
b.iter(|| {
let mut ctx = Context::new();
let todos = Vec::<Todo>::new();
let remaining = todos.iter().filter(|todo| !todo.completed).count();
let completed = todos.iter().filter(|todo| todo.completed).count();
ctx.insert("todos", &todos);
ctx.insert("main_class", &if todos.is_empty() { "hidden" } else { "" });
ctx.insert("toggle_checked", &(remaining > 0));
ctx.insert("todos_remaining", &remaining);
ctx.insert("todos_completed", &completed);
ctx.insert("todos_empty", &todos.is_empty());
ctx.insert("mode_all", &true);
ctx.insert("mode_active", &false);
ctx.insert("mode_selected", &false);
let _ = TERA.render("template.html", &ctx).unwrap();
});
}
#[bench]
fn tera_todomvc_1000(b: &mut Bencher) {
use serde::{Deserialize, Serialize};
use tera::*;
lazy_static::lazy_static! {
static ref TERA: Tera = {
let mut tera = Tera::default();
tera.add_raw_templates(vec![("template.html", TEMPLATE)]).unwrap();
tera
};
}
#[derive(Serialize, Deserialize)]
struct Todo {
id: usize,
label: String,
completed: bool,
editing: bool,
class: String,
}
b.iter(|| {
let mut ctx = Context::new();
let todos = (0..1000)
.map(|id| Todo {
id,
label: format!("Todo #{id}"),
completed: false,
editing: false,
class: "todo".to_string(),
})
.collect::<Vec<_>>();
let remaining = todos.iter().filter(|todo| !todo.completed).count();
let completed = todos.iter().filter(|todo| todo.completed).count();
ctx.insert("todos", &todos);
ctx.insert("main_class", &if todos.is_empty() { "hidden" } else { "" });
ctx.insert("toggle_checked", &(remaining > 0));
ctx.insert("todos_remaining", &remaining);
ctx.insert("todos_completed", &completed);
ctx.insert("todos_empty", &todos.is_empty());
ctx.insert("mode_all", &true);
ctx.insert("mode_active", &false);
ctx.insert("mode_selected", &false);
let _ = TERA.render("template.html", &ctx).unwrap();
});
}