mirror of
https://github.com/chenasraf/leptos.git
synced 2026-05-18 01:49:06 +00:00
Add Tera benchmarks, and list SSR benchmark results in README
This commit is contained in:
30
README.md
30
README.md
@@ -72,12 +72,34 @@ Most of the examples assume you’re using `nightly` Rust. If you’re 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
|
||||
|
||||
I’ve created a benchmark comparing Leptos’s 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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
extern crate test;
|
||||
|
||||
mod reactive;
|
||||
//mod reactive;
|
||||
mod ssr;
|
||||
mod todomvc;
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -2,6 +2,7 @@ use test::Bencher;
|
||||
|
||||
mod leptos;
|
||||
mod sycamore;
|
||||
mod tera;
|
||||
mod yew;
|
||||
|
||||
#[bench]
|
||||
|
||||
177
benchmarks/src/todomvc/tera.rs
Normal file
177
benchmarks/src/todomvc/tera.rs
Normal 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();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user