Benchmarks completely redone (#2352)

This commit is contained in:
Julius Lungys 2022-01-10 19:24:53 +02:00 committed by GitHub
parent 1c367a7b6f
commit 4be9308b84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1252 additions and 66 deletions

View File

@ -1,94 +1,234 @@
name: Benchmark
on:
push:
branches:
- master
pull_request:
if: >-
(github.event.action == 'labeled' && github.event.label.name == 'performance') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'performance'))
branches: [master]
types: [labeled, synchronize]
workflow_dispatch:
branches: [master]
permissions:
# deployments permission to deploy GitHub pages website
deployments: write
# contents permission to update benchmark contents in gh-pages branch
contents: write
jobs:
build:
results:
runs-on: ubuntu-latest
needs: benchmark
steps:
- uses: actions/checkout@v2
- run: |
touch results.json
echo '${{ needs.benchmark.outputs.results }}' >> results.json
# gh-pages branch is updated and pushed automatically with extracted benchmark data
- name: Store benchmark result
uses: benchmark-action/github-action-benchmark@v1
with:
name: "Yew master branch benchmarks (Lower is better)"
tool: "customSmallerIsBetter"
output-file-path: results.json
gh-pages-branch: "gh-pages"
# Access token to deploy GitHub Pages branch
github-token: ${{ secrets.GITHUB_TOKEN }}
# Push and deploy GitHub pages branch automatically
comment-always: true
alert-threshold: "150%"
comment-on-alert: true
alert-comment-cc-users: "@yewstack/yew"
# Don't push to gh-pages if its a pull request
auto-push: ${{ github.event_name != 'pull_request' }}
save-data-file: ${{ github.event_name != 'pull_request' }}
benchmark:
runs-on: ubuntu-latest
outputs:
results: ${{ steps.results.outputs.stdout }}
steps:
- uses: actions/checkout@v2
with:
repository: yewstack/js-framework-benchmark
path: "./yew"
- name: Configure benchmark
run: |
replace="yew = { git = \"${REPO_HTML_URL}\", branch = \"${BRANCH}\" }"
input=$(cat frameworks/keyed/yew/Cargo.toml)
output=$(echo "$input" | sed -e "s@yew = .*}@$replace@g")
if [[ "$input" == "$output" ]]; then
echo "ERROR: failed to configure Cargo.toml"
exit 1
fi
echo "$output" > frameworks/keyed/yew/Cargo.toml
echo "$output"
env:
# REPO_HTML_URL: ${{ github.event.pull_request.head.repo.html_url }}
REPO_HTML_URL: https://github.com/voidpumpkin/yew.git
# HEAD_REF: ${{ github.event.pull_request.head.ref }}
BRANCH: master
- uses: actions/checkout@v2
with:
repository: krausest/js-framework-benchmark
path: "./js-framework-benchmark"
- name: Setup ChromeDriver
uses: nanasess/setup-chromedriver@master
- name: Setup Rust
uses: actions-rs/toolchain@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown
override: true
profile: minimal
- uses: actions-rs/install@v0.1
- uses: jetli/wasm-pack-action@v0.3.0
with:
crate: wasm-bindgen-cli
version: latest
use-tool-cache: true
- uses: actions-rs/install@v0.1
with:
crate: wasm-pack
version: latest
use-tool-cache: true
- uses: actions-rs/install@v0.1
with:
crate: https
version: latest
use-tool-cache: true
- name: Start Server
run: http -p 8080 &
version: "latest"
- name: Setup Node
uses: actions/setup-node@v1
with:
node-version: 12
node-version: 16
- name: npm Install
run: |
npm install
(cd webdriver-ts && npm install)
(cd webdriver-ts-results && npm install)
- uses: Swatinem/rust-cache@v1
with:
working-directory: yew
- name: Build
- uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-benchmark-${{ hashFiles('js-framework-benchmark/package-lock.json') }}-${{ hashFiles('js-framework-benchmark/webdriver-ts/package-lock.json') }}
restore-keys: |
${{ runner.os }}-benchmark-
${{ runner.os }}
- name: save yew-struct setup
shell: bash
run: |
mkdir yew-struct-setup
cp -r js-framework-benchmark/frameworks/keyed/yew/* yew-struct-setup/
cd yew-struct-setup/bundled-dist
rm -rf ./*
# Will be enabled after https://github.com/krausest/js-framework-benchmark/pull/985 gets merged
# - name: save yew-hooks setup
# shell: bash
# run: |
# mkdir yew-hooks-setup
# cp -r js-framework-benchmark/frameworks/keyed/yew-hooks/* yew-hooks-setup/
# cd yew-hooks-setup/bundled-dist
# rm -rf ./*
- name: replace framework version in yew
shell: bash
run: |
replace=" \"frameworkVersion\": \"\""
input=$(cat yew-struct-setup/package.json)
output=$(echo "$input" | sed -e "s@\"frameworkVersion\": .*\"@$replace@g")
if [[ "$input" == "$output" ]]; then
echo "ERROR: failed to configure framework version"
exit 1
fi
echo "$output" > yew-struct-setup/package.json
echo "$output"
# Will be enabled after https://github.com/krausest/js-framework-benchmark/pull/985 gets merged
# - name: replace framework version in yew-hooks
# shell: bash
# run: |
# replace=" \"frameworkVersion\": \"\""
# input=$(cat yew-hooks-setup/package.json)
# output=$(echo "$input" | sed -e "s@\"frameworkVersion\": .*\"@$replace@g")
# if [[ "$input" == "$output" ]]; then
# echo "ERROR: failed to configure framework version"
# exit 1
# fi
# echo "$output" > yew-hooks-setup/package.json
# echo "$output"
- name: delete all frameworks
shell: bash
run: |
cd js-framework-benchmark/frameworks/keyed
rm -rf ./*
cd ../non-keyed
rm -rf ./*
- name: create framework folders
shell: bash
run: |
cd js-framework-benchmark/frameworks/keyed
mkdir yew-struct
# Will be enabled after https://github.com/krausest/js-framework-benchmark/pull/985 gets merged
# mkdir yew-hooks
- name: copy necessary framework files
shell: bash
run: |
cp -r yew-struct-setup/* js-framework-benchmark/frameworks/keyed/yew-struct/
# Will be enabled after https://github.com/krausest/js-framework-benchmark/pull/985 gets merged
# cp -r yew-hooks-setup/* js-framework-benchmark/frameworks/keyed/yew-hooks/
- name: build benchmark-struct app
shell: bash
run: |
cd yew/tools/benchmark-struct
npm ci
npm run build-prod-without-tools-install
- name: build benchmark-hooks app
shell: bash
run: |
cd yew/tools/benchmark-hooks
npm ci
npm run build-prod-without-tools-install
- name: move dist files
shell: bash
run: |
mv yew/tools/benchmark-struct/bundled-dist js-framework-benchmark/frameworks/keyed/yew-struct/
# Will be enabled after https://github.com/krausest/js-framework-benchmark/pull/985 gets merged
# mv yew/tools/benchmark-hooks/bundled-dist js-framework-benchmark/frameworks/keyed/yew-hooks/
- name: js-framework-benchmark npm ci
shell: bash
run: |
cd js-framework-benchmark
npm ci
- name: js-framework-benchmark npm start
shell: bash
run: |
cd js-framework-benchmark
npm start &
- name: js-framework-benchmark/webdriver-ts npm ci
shell: bash
run: |
cd js-framework-benchmark/webdriver-ts
npm ci
npm install chromedriver --chromedriver-force-download
- name: js-framework-benchmark/webdriver-ts npm run compile
shell: bash
run: |
cd js-framework-benchmark/webdriver-ts
npm run compile
- name: js-framework-benchmark npm run build-prod
shell: bash
run: |
cd js-framework-benchmark
npm run build-prod
(cd webdriver-ts && npm run build-prod)
- name: Benchmark
run: npm run bench -- --headless
- name: Results
run: npm run results
- name: Log results
- name: js-framework-benchmark/webdriver-ts npm run bench
shell: bash
run: |
msg=$(cd results_diff && cargo run)
echo "$msg"
cd js-framework-benchmark/webdriver-ts
npm run bench -- --headless
- name: transform results into json
shell: bash
run: |
cd js-framework-benchmark/webdriver-ts/results
touch temp.txt
echo "[" >> temp.txt
for filename in *.json; do cat ${filename} >> temp.txt; echo "," >> temp.txt; done
sed -i '$ s/.$//' temp.txt #remove trailing comma
echo "]" >> temp.txt
mv temp.txt results.json
- name: Build process-benchmark-results
shell: bash
run: |
cd yew
cargo build --release -p process-benchmark-results
- name: transform results to be fit for display benchmark-action/github-action-benchmark@v1
uses: mathiasvr/command-output@v1
id: results
with:
run: cat js-framework-benchmark/webdriver-ts/results/results.json | ./yew/target/release/process-benchmark-results

View File

@ -35,6 +35,9 @@ members = [
# Tools
"tools/changelog",
"tools/process-benchmark-results",
"tools/benchmark-struct",
"tools/benchmark-hooks",
]
exclude = [
# Tools

4
tools/benchmark-hooks/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules
/target
/bundled-dist

View File

@ -0,0 +1,23 @@
[package]
name = "js-framework-benchmark-yew-hooks"
version = "1.0.0"
authors = ["Julius Lungys <juliuslungys@gmail.com>"]
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
rand = { version = "0.8.4", features = ["small_rng"] }
getrandom = { version = "0.2.1", features = ["js"] }
wasm-bindgen = "0.2.78"
web-sys = { version = "0.3.55", features = ["Window"]}
yew = "0.19.3"
[profile.release]
lto = true
codegen-units = 1
panic = "abort"
[package.metadata.wasm-pack.profile.release]
wasm-opt = ['-O4']

View File

@ -0,0 +1,6 @@
Copied from https://github.com/krausest/js-framework-benchmark
It should ideally stay/be updated to always match what is on https://github.com/krausest/js-framework-benchmark
Except the fixes required to tun using unreleased yew version.
If you want to improve benchmarks, consider first opening a PR to https://github.com/krausest/js-framework-benchmark and only then sync this package with it.

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Yew-Hooks</title>
<link href="/css/currentStyle.css" rel="stylesheet"/>
<base href="bundled-dist/"></base>
</head>
<body>
<div id='main'></div>
<script type="module">
import init from './js-framework-benchmark-yew-hooks.js';
init('./js-framework-benchmark-yew-hooks_bg.wasm');
</script>
</body>
</html>

138
tools/benchmark-hooks/package-lock.json generated Normal file
View File

@ -0,0 +1,138 @@
{
"name": "js-framework-benchmark-non-keyed-yew",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"cpr": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/cpr/-/cpr-3.0.1.tgz",
"integrity": "sha1-uaVQOLfNgaNcF7l2GJW9hJau8eU=",
"dev": true,
"requires": {
"graceful-fs": "^4.1.5",
"minimist": "^1.2.0",
"mkdirp": "~0.5.1",
"rimraf": "^2.5.4"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"graceful-fs": {
"version": "4.2.9",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
"integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
}
}
}

View File

@ -0,0 +1,22 @@
{
"name": "js-framework-benchmark-non-keyed-yew-hooks",
"version": "1.0.0",
"description": "Benchmark for Yew Hooks",
"license": "ISC",
"js-framework-benchmark": {
"frameworkVersion": "latest"
},
"scripts": {
"build-prod": "echo This is a no-op. && echo Due to heavy dependencies, the generated javascript is already provided. && echo If you really want to rebuild from source use: && echo npm run build-prod-force",
"build-prod-force": "rustup target add wasm32-unknown-unknown && cargo install wasm-pack && npx build-prod-without-tools-install",
"build-prod-without-tools-install": "rimraf bundled-dist && wasm-pack build --release --target web --no-typescript --out-name js-framework-benchmark-yew-hooks --out-dir bundled-dist && cpr index.html bundled-dist/index.html && (cd bundled-dist && rimraf .gitignore README.md package.json)"
},
"repository": {
"type": "git",
"url": "https://github.com/krausest/js-framework-benchmark.git"
},
"devDependencies": {
"rimraf": "^2.6.3",
"cpr": "^3.0.1"
}
}

View File

@ -0,0 +1,290 @@
use rand::prelude::*;
use std::cmp::min;
use std::ops::Deref;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use web_sys::window;
use yew::prelude::*;
static ADJECTIVES: &[&str] = &[
"pretty",
"large",
"big",
"small",
"tall",
"short",
"long",
"handsome",
"plain",
"quaint",
"clean",
"elegant",
"easy",
"angry",
"crazy",
"helpful",
"mushy",
"odd",
"unsightly",
"adorable",
"important",
"inexpensive",
"cheap",
"expensive",
"fancy",
];
static COLOURS: &[&str] = &[
"red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
"orange",
];
static NOUNS: &[&str] = &[
"table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
"pizza", "mouse", "keyboard",
];
#[derive(Clone, PartialEq)]
struct RowData {
id: usize,
label: String,
}
impl RowData {
fn new(id: usize, rng: &mut SmallRng) -> Self {
let adjective = *ADJECTIVES.choose(rng).unwrap();
let colour = *COLOURS.choose(rng).unwrap();
let noun = *NOUNS.choose(rng).unwrap();
let label = [adjective, colour, noun].join(" ");
Self { id, label }
}
}
enum AppStateAction {
Run(usize),
Add(usize),
Update(usize),
Clear,
Swap,
Remove(usize),
Select(usize),
}
#[derive(Clone)]
struct AppState {
next_id: usize,
selected_id: Option<usize>,
rows: Vec<RowData>,
rng: SmallRng,
}
impl Default for AppState {
fn default() -> Self {
Self {
rows: Vec::new(),
next_id: 1,
selected_id: None,
rng: SmallRng::from_entropy(),
}
}
}
impl Reducible for AppState {
type Action = AppStateAction;
fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
let mut new_state = self.deref().clone();
match action {
AppStateAction::Run(amount) => {
let rng = &mut new_state.rng;
let next_id = new_state.next_id;
let update_amount = min(amount, new_state.rows.len());
for index in 0..update_amount {
new_state.rows[index] = RowData::new(next_id + index, rng);
}
new_state.rows.extend(
(update_amount..amount).map(|index| RowData::new(next_id + index, rng)),
);
new_state.next_id += amount;
}
AppStateAction::Add(amount) => {
let rng = &mut new_state.rng;
let next_id = new_state.next_id;
new_state
.rows
.extend((0..amount).map(|index| RowData::new(next_id + index, rng)));
new_state.next_id += amount;
}
AppStateAction::Update(step) => {
for index in (0..new_state.rows.len()).step_by(step) {
new_state.rows[index].label += " !!!";
}
}
AppStateAction::Clear => {
new_state.rows.clear();
}
AppStateAction::Swap => {
if new_state.rows.len() > 998 {
new_state.rows.swap(1, 998);
}
}
AppStateAction::Remove(id) => {
if let Some(index) = new_state.rows.iter().position(|row| row.id == id) {
new_state.rows.remove(index);
}
}
AppStateAction::Select(id) => {
new_state.selected_id = Some(id);
}
};
new_state.into()
}
}
#[function_component(App)]
fn app() -> Html {
let state = use_reducer(AppState::default);
let selected_id = state.deref().selected_id;
let rows = state.deref().rows.clone();
let on_run = {
let state = state.clone();
Callback::from(move |amount| state.dispatch(AppStateAction::Run(amount)))
};
let on_add = {
let state = state.clone();
Callback::from(move |amount| state.dispatch(AppStateAction::Add(amount)))
};
let on_update = {
let state = state.clone();
Callback::from(move |amount| state.dispatch(AppStateAction::Update(amount)))
};
let on_clear = {
let state = state.clone();
Callback::from(move |_| state.dispatch(AppStateAction::Clear))
};
let on_swap = {
let state = state.clone();
Callback::from(move |_| state.dispatch(AppStateAction::Swap))
};
let on_select = {
let state = state.clone();
Callback::from(move |id| state.dispatch(AppStateAction::Select(id)))
};
let on_remove = Callback::from(move |id| state.dispatch(AppStateAction::Remove(id)));
let rows: Html = rows
.into_iter()
.map(move |row| {
let id = row.id;
html! {
<Row
key={id}
data={row}
selected={selected_id == Some(id)}
on_select={on_select.reform(move |_| id)}
on_remove={on_remove.reform(move |_| id)}
/>
}
})
.collect();
html! {
<div class="container">
<Jumbotron
{on_run}
{on_add}
{on_update}
{on_clear}
{on_swap}
/>
<table class="table table-hover table-striped test-data">
<tbody id="tbody">
{ rows }
</tbody>
</table>
<span class="preloadicon glyphicon glyphicon-remove" aria-hidden="true"></span>
</div>
}
}
#[derive(Properties, Clone, PartialEq)]
pub struct JumbotronProps {
pub on_run: Callback<usize>,
pub on_add: Callback<usize>,
pub on_update: Callback<usize>,
pub on_clear: Callback<()>,
pub on_swap: Callback<()>,
}
#[function_component(Jumbotron)]
fn jumbotron(props: &JumbotronProps) -> Html {
html! {
<div class="jumbotron">
<div class="row">
<div class="col-md-6">
<h1>{ "Yew" }</h1>
</div>
<div class="col-md-6">
<div class="row">
<div class="col-sm-6 smallpad">
<button type="button" id="run" class="btn btn-primary btn-block" onclick={props.on_run.reform(|_| 1_000)}>{ "Create 1,000 rows" }</button>
</div>
<div class="col-sm-6 smallpad">
<button type="button" class="btn btn-primary btn-block" onclick={props.on_run.reform(|_| 10_000)} id="runlots">{ "Create 10,000 rows" }</button>
</div>
<div class="col-sm-6 smallpad">
<button type="button" class="btn btn-primary btn-block" onclick={props.on_add.reform(|_| 1_000)} id="add">{ "Append 1,000 rows" }</button>
</div>
<div class="col-sm-6 smallpad">
<button type="button" class="btn btn-primary btn-block" onclick={props.on_update.reform(|_| 10)} id="update">{ "Update every 10th row" }</button>
</div>
<div class="col-sm-6 smallpad">
<button type="button" class="btn btn-primary btn-block" onclick={props.on_clear.reform(|_| ())} id="clear">{ "Clear" }</button>
</div>
<div class="col-sm-6 smallpad">
<button type="button" class="btn btn-primary btn-block" onclick={props.on_swap.reform(|_| ())} id="swaprows">{ "Swap Rows" }</button>
</div>
</div>
</div>
</div>
</div>
}
}
#[derive(Properties, Clone, PartialEq)]
struct RowProps {
on_select: Callback<MouseEvent>,
on_remove: Callback<MouseEvent>,
selected: bool,
data: RowData,
}
#[function_component(Row)]
fn row(props: &RowProps) -> Html {
html! {
<tr class={if props.selected { "danger" } else { "" }}>
<td class="col-md-1">{ props.data.id }</td>
<td class="col-md-4" onclick={props.on_select.clone()}>
<a class="lbl">{ props.data.label.clone() }</a>
</td>
<td class="col-md-1">
<a class="remove" onclick={props.on_remove.clone()}>
<span class="glyphicon glyphicon-remove remove" aria-hidden="true"></span>
</a>
</td>
<td class="col-md-6"></td>
</tr>
}
}
#[wasm_bindgen(start)]
pub fn start() {
let document = window().unwrap().document().unwrap();
let mount_el = document.query_selector("#main").unwrap().unwrap();
yew::start_app_in_element::<App>(mount_el);
}

3
tools/benchmark-struct/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
/target
/bundled-dist

View File

@ -0,0 +1,23 @@
[package]
name = "js-framework-benchmark-yew"
version = "1.0.0"
authors = ["Isamu Mogi <isamu@leafytree.jp>"]
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
rand = { version = "0.8.4", features = ["small_rng"] }
getrandom = { version = "0.2.1", features = ["js"] }
wasm-bindgen = "0.2.78"
web-sys = { version = "0.3.55", features = ["Window"]}
yew = "0.19.3"
[profile.release]
lto = true
codegen-units = 1
panic = "abort"
[package.metadata.wasm-pack.profile.release]
wasm-opt = ['-O4']

View File

@ -0,0 +1,6 @@
Copied from https://github.com/krausest/js-framework-benchmark
It should ideally stay/be updated to always match what is on https://github.com/krausest/js-framework-benchmark
Except the fixes required to tun using unreleased yew version.
If you want to improve benchmarks, consider first opening a PR to https://github.com/krausest/js-framework-benchmark and only then sync this package with it.

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Yew</title>
<link href="/css/currentStyle.css" rel="stylesheet"/>
<base href="bundled-dist/"></base>
</head>
<body>
<div id='main'></div>
<script type="module">
import init from './js-framework-benchmark-yew.js';
init('./js-framework-benchmark-yew_bg.wasm');
</script>
</body>
</html>

138
tools/benchmark-struct/package-lock.json generated Normal file
View File

@ -0,0 +1,138 @@
{
"name": "js-framework-benchmark-non-keyed-yew",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"cpr": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/cpr/-/cpr-3.0.1.tgz",
"integrity": "sha1-uaVQOLfNgaNcF7l2GJW9hJau8eU=",
"dev": true,
"requires": {
"graceful-fs": "^4.1.5",
"minimist": "^1.2.0",
"mkdirp": "~0.5.1",
"rimraf": "^2.5.4"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"graceful-fs": {
"version": "4.2.9",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
"integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
}
}
}

View File

@ -0,0 +1,22 @@
{
"name": "js-framework-benchmark-non-keyed-yew",
"version": "1.0.0",
"description": "Benchmark for Yew",
"license": "ISC",
"js-framework-benchmark": {
"frameworkVersion": "latest"
},
"scripts": {
"build-prod": "echo This is a no-op. && echo Due to heavy dependencies, the generated javascript is already provided. && echo If you really want to rebuild from source use: && echo npm run build-prod-force",
"build-prod-force": "rustup target add wasm32-unknown-unknown && cargo install wasm-pack && npx build-prod-without-tools-install",
"build-prod-without-tools-install": "rimraf bundled-dist && wasm-pack build --release --target web --no-typescript --out-name js-framework-benchmark-yew --out-dir bundled-dist && cpr index.html bundled-dist/index.html && (cd bundled-dist && rimraf .gitignore README.md package.json)"
},
"repository": {
"type": "git",
"url": "https://github.com/krausest/js-framework-benchmark.git"
},
"devDependencies": {
"rimraf": "^2.6.3",
"cpr": "^3.0.1"
}
}

View File

@ -0,0 +1,289 @@
use rand::prelude::*;
use std::cmp::min;
use wasm_bindgen::prelude::*;
use web_sys::window;
use yew::prelude::*;
static ADJECTIVES: &[&str] = &[
"pretty",
"large",
"big",
"small",
"tall",
"short",
"long",
"handsome",
"plain",
"quaint",
"clean",
"elegant",
"easy",
"angry",
"crazy",
"helpful",
"mushy",
"odd",
"unsightly",
"adorable",
"important",
"inexpensive",
"cheap",
"expensive",
"fancy",
];
static COLOURS: &[&str] = &[
"red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
"orange",
];
static NOUNS: &[&str] = &[
"table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
"pizza", "mouse", "keyboard",
];
#[derive(Clone, PartialEq)]
struct RowData {
id: usize,
label: String,
}
impl RowData {
fn new(id: usize, rng: &mut SmallRng) -> Self {
let adjective = *ADJECTIVES.choose(rng).unwrap();
let colour = *COLOURS.choose(rng).unwrap();
let noun = *NOUNS.choose(rng).unwrap();
let label = [adjective, colour, noun].join(" ");
Self { id, label }
}
}
struct App {
rows: Vec<RowData>,
next_id: usize,
selected_id: Option<usize>,
rng: SmallRng,
on_select: Callback<usize>,
on_remove: Callback<usize>,
}
enum Msg {
Run(usize),
Add(usize),
Update(usize),
Clear,
Swap,
Remove(usize),
Select(usize),
}
impl Component for App {
type Message = Msg;
type Properties = ();
fn create(ctx: &Context<Self>) -> Self {
App {
rows: Vec::new(),
next_id: 1,
selected_id: None,
rng: SmallRng::from_entropy(),
on_select: ctx.link().callback(Msg::Select),
on_remove: ctx.link().callback(Msg::Remove),
}
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::Run(amount) => {
let rng = &mut self.rng;
let next_id = self.next_id;
let update_amount = min(amount, self.rows.len());
for index in 0..update_amount {
self.rows[index] = RowData::new(next_id + index, rng);
}
self.rows.extend(
(update_amount..amount).map(|index| RowData::new(next_id + index, rng)),
);
self.next_id += amount;
}
Msg::Add(amount) => {
let rng = &mut self.rng;
let next_id = self.next_id;
self.rows
.extend((0..amount).map(|index| RowData::new(next_id + index, rng)));
self.next_id += amount;
}
Msg::Update(step) => {
for index in (0..self.rows.len()).step_by(step) {
self.rows[index].label += " !!!";
}
}
Msg::Clear => {
self.rows.clear();
}
Msg::Swap => {
if self.rows.len() > 998 {
self.rows.swap(1, 998);
}
}
Msg::Remove(id) => {
if let Some(index) = self.rows.iter().position(|row| row.id == id) {
self.rows.remove(index);
}
}
Msg::Select(id) => {
self.selected_id = Some(id);
}
}
true
}
fn view(&self, ctx: &Context<Self>) -> Html {
let rows: Html = self
.rows
.iter()
.map(|row| {
html! {
<Row
key={row.id}
data={row.clone()}
selected={self.selected_id == Some(row.id)}
on_select={self.on_select.clone()}
on_remove={self.on_remove.clone()}
/>
}
})
.collect();
html! {
<div class="container">
<Jumbotron
on_run={ctx.link().callback(Msg::Run)}
on_add={ctx.link().callback(Msg::Add)}
on_update={ctx.link().callback(Msg::Update)}
on_clear={ctx.link().callback(|_| Msg::Clear)}
on_swap={ctx.link().callback(|_| Msg::Swap)}
/>
<table class="table table-hover table-striped test-data">
<tbody id="tbody">
{ rows }
</tbody>
</table>
<span class="preloadicon glyphicon glyphicon-remove" aria-hidden="true"></span>
</div>
}
}
}
#[derive(Properties, Clone, PartialEq)]
pub struct JumbotronProps {
pub on_run: Callback<usize>,
pub on_add: Callback<usize>,
pub on_update: Callback<usize>,
pub on_clear: Callback<()>,
pub on_swap: Callback<()>,
}
pub struct Jumbotron {}
impl Component for Jumbotron {
type Properties = JumbotronProps;
type Message = ();
fn create(_ctx: &Context<Self>) -> Self {
Self {}
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<div class="jumbotron">
<div class="row">
<div class="col-md-6">
<h1>{ "Yew-Hooks" }</h1>
</div>
<div class="col-md-6">
<div class="row">
<div class="col-sm-6 smallpad">
<button type="button" id="run" class="btn btn-primary btn-block" onclick={ctx.props().on_run.reform(|_| 1_000)}>{ "Create 1,000 rows" }</button>
</div>
<div class="col-sm-6 smallpad">
<button type="button" class="btn btn-primary btn-block" onclick={ctx.props().on_run.reform(|_| 10_000)} id="runlots">{ "Create 10,000 rows" }</button>
</div>
<div class="col-sm-6 smallpad">
<button type="button" class="btn btn-primary btn-block" onclick={ctx.props().on_add.reform(|_| 1_000)} id="add">{ "Append 1,000 rows" }</button>
</div>
<div class="col-sm-6 smallpad">
<button type="button" class="btn btn-primary btn-block" onclick={ctx.props().on_update.reform(|_| 10)} id="update">{ "Update every 10th row" }</button>
</div>
<div class="col-sm-6 smallpad">
<button type="button" class="btn btn-primary btn-block" onclick={ctx.props().on_clear.reform(|_| ())} id="clear">{ "Clear" }</button>
</div>
<div class="col-sm-6 smallpad">
<button type="button" class="btn btn-primary btn-block" onclick={ctx.props().on_swap.reform(|_| ())} id="swaprows">{ "Swap Rows" }</button>
</div>
</div>
</div>
</div>
</div>
}
}
}
#[derive(Properties, Clone, PartialEq)]
struct RowProps {
on_select: Callback<usize>,
on_remove: Callback<usize>,
selected: bool,
data: RowData,
}
struct Row {
on_select: Callback<MouseEvent>,
on_remove: Callback<MouseEvent>,
}
impl Component for Row {
type Properties = RowProps;
type Message = ();
fn create(ctx: &Context<Self>) -> Self {
let id = ctx.props().data.id;
Self {
on_select: ctx.props().on_select.reform(move |_| id),
on_remove: ctx.props().on_remove.reform(move |_| id),
}
}
fn changed(&mut self, ctx: &Context<Self>) -> bool {
let id = ctx.props().data.id;
self.on_select = ctx.props().on_select.reform(move |_| id);
self.on_remove = ctx.props().on_remove.reform(move |_| id);
true
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<tr class={if ctx.props().selected { "danger" } else { "" }}>
<td class="col-md-1">{ ctx.props().data.id }</td>
<td class="col-md-4" onclick={self.on_select.clone()}>
<a class="lbl">{ ctx.props().data.label.clone() }</a>
</td>
<td class="col-md-1">
<a class="remove" onclick={self.on_remove.clone()}>
<span class="glyphicon glyphicon-remove remove" aria-hidden="true"></span>
</a>
</td>
<td class="col-md-6"></td>
</tr>
}
}
}
#[wasm_bindgen(start)]
pub fn start() {
let document = window().unwrap().document().unwrap();
let mount_el = document.query_selector("#main").unwrap().unwrap();
yew::start_app_in_element::<App>(mount_el);
}

View File

@ -0,0 +1,11 @@
[package]
name = "process-benchmark-results"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0"

View File

@ -0,0 +1,3 @@
Processes benchmark results
From array of js-framework-benchmark
Into array that works for https://github.com/marketplace/actions/continuous-benchmark action

View File

@ -0,0 +1,33 @@
use anyhow::Result;
use serde::Serialize;
use serde_json::Value;
use std::io;
use std::io::{Read, Write};
#[derive(Serialize)]
struct GhActionBenchmark {
name: String,
unit: String,
value: String,
}
fn main() -> Result<()> {
let mut buffer = "".to_string();
io::stdin().read_to_string(&mut buffer)?;
let input_json: Vec<Value> = serde_json::from_str(buffer.as_str())?;
let transformed_benchmarks: Vec<GhActionBenchmark> = input_json
.into_iter()
.map(|v| GhActionBenchmark {
name: format!("{} {}", v["framework"], v["benchmark"]).replace("\"", ""),
unit: String::default(),
value: v["median"].to_string(),
})
.collect();
let output = serde_json::to_string(&transformed_benchmarks)?;
io::stdout().write_all(output.as_bytes())?;
Ok(())
}