mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-11-23 12:02:40 +03:00
Add a page of microbenchmarks for wasm-bindgen
This commit starts to add a page of microbenchmarks for wasm-bindgen which we can hopefully track and compare over time. Right now it's primarily focused on data collection, making it easy to collect data across a number of benchmarks for comparison. It doesn't currently do much in the way of actually comparing the results for you (aka drawing pretty graphs), so let's left for a future step. It's hoped though that we can use this to track performance improvements as well as ensuring that they work over time!
This commit is contained in:
parent
a7b85362ce
commit
e4fd0fccb5
@ -48,6 +48,7 @@ wasm-bindgen-test-crate-b = { path = 'tests/crates/b', version = '0.1' }
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"benchmarks",
|
||||
"crates/cli",
|
||||
"crates/js-sys",
|
||||
"crates/macro/ui-tests",
|
||||
|
@ -144,15 +144,9 @@ jobs:
|
||||
steps:
|
||||
- template: ci/azure-install-rust.yml
|
||||
- template: ci/azure-install-sccache.yml
|
||||
- template: ci/azure-install-wasm-pack.yml
|
||||
- script: mv _package.json package.json && npm install && rm package.json
|
||||
displayName: "run npm install"
|
||||
- script: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
|
||||
displayName: "install wasm-pack"
|
||||
- script: |
|
||||
set -ex
|
||||
cargo build -p wasm-bindgen-cli
|
||||
ln -snf `pwd`/target/debug/wasm-bindgen $HOME/.cargo/bin/wasm-bindgen
|
||||
displayName: "install wasm-bindgen for `wasm-pack` to use"
|
||||
- script: |
|
||||
for dir in `ls examples | grep -v README | grep -v asm.js | grep -v raytrace | grep -v without-a-bundler`; do
|
||||
(cd examples/$dir &&
|
||||
@ -190,6 +184,19 @@ jobs:
|
||||
artifactName: examples2
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)'
|
||||
|
||||
- job: build_benchmarks
|
||||
displayName: "Build benchmarks"
|
||||
steps:
|
||||
- template: ci/azure-install-rust.yml
|
||||
- template: ci/azure-install-sccache.yml
|
||||
- template: ci/azure-install-wasm-pack.yml
|
||||
- script: wasm-pack build --target web benchmarks
|
||||
displayName: "build benchmarks"
|
||||
- task: PublishPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: benchmarks
|
||||
targetPath: benchmarks
|
||||
|
||||
- job: dist_linux
|
||||
displayName: "Dist Linux binary"
|
||||
steps:
|
||||
@ -292,6 +299,7 @@ jobs:
|
||||
- dist_windows
|
||||
- build_examples
|
||||
- build_raytrace
|
||||
- build_benchmarks
|
||||
displayName: "Deploy everything"
|
||||
steps:
|
||||
- template: ci/azure-install-rust.yml
|
||||
@ -315,6 +323,11 @@ jobs:
|
||||
inputs:
|
||||
artifactName: examples2
|
||||
targetPath: gh-pages/exbuild/raytrace-parallel
|
||||
- task: DownloadPipelineArtifact@0
|
||||
displayName: "Download benchmarks"
|
||||
inputs:
|
||||
artifactName: benchmarks
|
||||
targetPath: gh-pages/benchmarks
|
||||
- task: DownloadPipelineArtifact@0
|
||||
displayName: "Download dist - windows"
|
||||
inputs:
|
||||
|
11
benchmarks/Cargo.toml
Normal file
11
benchmarks/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "wasm-bindgen-benchmark"
|
||||
version = "0.1.0"
|
||||
authors = ["The wasm-bindgen Developers"]
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = "0.2.43"
|
||||
web-sys = { version = "0.3.20", features = ['Node'] }
|
||||
|
||||
[lib]
|
||||
crate-type = ['cdylib']
|
54
benchmarks/README.md
Normal file
54
benchmarks/README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Microbenchmarks for `wasm-bindgen`
|
||||
|
||||
This folder houses a number of microbenchmarks for `wasm-bindgen`. These, like
|
||||
all microbenchmarks, should be taken with a grain of salt. They are intended to
|
||||
help developers understand changes over time, but they are not intended to be a
|
||||
performance suite for WebAssembly for Rust.
|
||||
|
||||
[View benchmarks for `master` branch online][online]
|
||||
|
||||
[online]: https://rustwasm.github.io/wasm-bindgen/benchmarks/
|
||||
|
||||
## Building and Running
|
||||
|
||||
First, copy the benchmarks to a temporary directory:
|
||||
|
||||
```
|
||||
$ cp ./benchmarks /some/other/directory
|
||||
```
|
||||
|
||||
Next, `cd` into that directory and execute:
|
||||
|
||||
```
|
||||
$ wasm-pack build --target web
|
||||
```
|
||||
|
||||
Next, use your favorite static file server to host the current directory. For
|
||||
example using the [`https` crate](https://crates.io/crates/https):
|
||||
|
||||
```
|
||||
$ http
|
||||
```
|
||||
|
||||
Then open up a web browser and view http://localhost:8000, for example.
|
||||
|
||||
You should be presented a page with lots of `(run)` links, where when you click
|
||||
them it will execute the benchmark and then display the result.
|
||||
|
||||
## Benchmark Architecture
|
||||
|
||||
Currently benchmarks are pretty bare bones. They just use benchmark.js to
|
||||
generate statistics which are then rendered to the screen. Benchmarks are listed
|
||||
one-by-one in `index.html` where a `td` exists for each benchmark. In `index.js`
|
||||
each of the `td`'s `id` properties are hooked up to an actual function to
|
||||
benchmark, depending on what's being benchmarked.
|
||||
|
||||
Relevant files are:
|
||||
|
||||
* `index.html` - the page showing all benchmarks
|
||||
* `index.js` - the driver JS for all benchmarks
|
||||
* `globals.js` - global JS functions imported by all other benchmarks
|
||||
* `js-bencharks.js` - the JS functions that we're benchmarking
|
||||
* `src/lib.rs` - the Rust/`wasm-bindgen` functions we're benchmarking
|
||||
* `raw.wast`/`raw.wasm` - a raw handwritten WebAssembly file used in some
|
||||
benchmarks. A compiled version of this is checked into the repository.
|
5
benchmarks/globals.js
Normal file
5
benchmarks/globals.js
Normal file
@ -0,0 +1,5 @@
|
||||
export function jsthunk() {}
|
||||
export function add(a, b) { return a + b; }
|
||||
export class Foo {
|
||||
bar() {}
|
||||
}
|
295
benchmarks/index.html
Normal file
295
benchmarks/index.html
Normal file
@ -0,0 +1,295 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
|
||||
<style>
|
||||
|
||||
body {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
thead td {
|
||||
font-weight: bold;
|
||||
}
|
||||
table td {
|
||||
border: 1px solid black;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.about {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script src='index.js' type=module async></script>
|
||||
</head>
|
||||
<body>
|
||||
<a href='https://github.com/alexcrichton/rust-wasm-benchmark'>Source code</a>
|
||||
|
||||
<h1>JS / wasm-bindgen comparison</h1>
|
||||
|
||||
<p>
|
||||
These benchmarks are meant to compare WebAssembly costs using raw wasm
|
||||
files and wasm-bindgen itself against the JS equivalents. These
|
||||
microbenchmarks aren't really representative of WebAssembly performance,
|
||||
but can be useful data points about how expensive it is to cross
|
||||
boundaries for example.
|
||||
</p>
|
||||
<p>
|
||||
For all benchmarks higher numbers are better numbers.
|
||||
</p>
|
||||
|
||||
<table id='js-vs-wasm-bindgen'>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Benchmark</td>
|
||||
<td>wasm-bindgen</td>
|
||||
<td>JS</td>
|
||||
<td><code>*.wast</code></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
Call a thunk
|
||||
|
||||
<a class='about-open' href='#'>(?)</a>
|
||||
|
||||
<p class='about'>
|
||||
This benchmarks tests out how long it take JS to call a thunk in
|
||||
the given language. For example JS will call a JS thunk or
|
||||
JS will call a wasm function that does nothing.
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<td class='bm' id='wbindgen_thunk'></td>
|
||||
<td class='bm' id='js_thunk'></td>
|
||||
<td class='bm' id='raw_thunk'></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Call an adder
|
||||
|
||||
<a class='about-open' href='#'>(?)</a>
|
||||
|
||||
<p class='about'>
|
||||
This benchmarks tests out how long it take JS to call a function
|
||||
in the target language which adds two numbers and returns the
|
||||
result. This is likely to be similar to the previous benchmark
|
||||
in terms of results, but it's thought that some extra computation
|
||||
may show overheads differently.
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<td class='bm' id='wbindgen_thunk'></td>
|
||||
<td class='bm' id='js_thunk'></td>
|
||||
<td class='bm' id='raw_thunk'></td>
|
||||
</tr>
|
||||
|
||||
<td>
|
||||
Call a JS thunk
|
||||
|
||||
<a class='about-open' href='#'>(?)</a>
|
||||
|
||||
<p class='about'>
|
||||
This benchmarks tests out how long it takes to call a JS function
|
||||
10,000 times. Each language/framework has a function which takes a
|
||||
parameter of how many times to call an imported JS function.
|
||||
Remember that JS itself benefits from inlining, where as JS cannot
|
||||
be inlined into WebAssembly. In these cases the imported function
|
||||
performs no work, it just returns immediately.
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<td class='bm' id='wbindgen_call_js_thunk_n_times'></td>
|
||||
<td class='bm' id='js_call_js_thunk_n_times'></td>
|
||||
<td class='bm' id='raw_call_js_thunk_n_times'></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
Call a JS function which adds
|
||||
|
||||
<a class='about-open' href='#'>(?)</a>
|
||||
|
||||
<p class='about'>
|
||||
This benchmark is similar to the previous thunk benchmark except
|
||||
that this time the imported function will add its two arguments
|
||||
and returns the result. This helps measure the overhead of
|
||||
sending data like integers back and forth.
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<td class='bm' id='wbindgen_call_js_add_n_times'></td>
|
||||
<td class='bm' id='js_call_js_add_n_times'></td>
|
||||
<td class='bm' id='raw_call_js_add_n_times'></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
Calculate Fib(40)
|
||||
|
||||
<a class='about-open' href='#'>(?)</a>
|
||||
|
||||
<p class='about'>
|
||||
This benchmarks calculates the 40th fibonacci number. It in
|
||||
theory should favor wasm since wasm is "better a compute", but
|
||||
a good JIT will probably make the code roughly equivalent.
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<td class='bm' id='wbindgen_fib_40'></td>
|
||||
<td class='bm' id='js_fib_40'></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
Access <code>Node.firstChild</code>
|
||||
|
||||
<a class='about-open' href='#'>(?)</a>
|
||||
|
||||
<p class='about'>
|
||||
This benchmark attempts to see the cost of accessing a DOM
|
||||
property from both JS and WebAssembly. We access the DOM property
|
||||
as fast as possible in WebAssembly and otherwise just access it
|
||||
as a normal property in JS.
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<td class='bm' id='wbindgen_call_node_first_child_n_times'></td>
|
||||
<td class='bm' id='js_call_node_first_child_n_times'></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
Access <code>Node.nodeType</code>
|
||||
|
||||
<a class='about-open' href='#'>(?)</a>
|
||||
|
||||
<p class='about'>
|
||||
This benchmark attempts to see the cost of accessing a DOM
|
||||
property from both JS and WebAssembly. We access the DOM property
|
||||
as fast as possible in WebAssembly and otherwise just access it
|
||||
as a normal property in JS.
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<td class='bm' id='wbindgen_call_node_node_type_n_times'></td>
|
||||
<td class='bm' id='js_call_node_node_type_n_times'></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
Count types of nodes on a page
|
||||
|
||||
<a class='about-open' href='#'>(?)</a>
|
||||
|
||||
<p class='about'>
|
||||
This is intended to be a "flavorful DOM benchmark" which
|
||||
exercises DOM functionality from WebAssembly, specifically
|
||||
counting the number of types of each node on a page.
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<td class='bm' id='wbindgen_count_node_types'></td>
|
||||
<td class='bm' id='js_count_node_types'></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h1>wasm-bindgen benchmarks</h1>
|
||||
|
||||
<p>
|
||||
These benchmarks don't compare against JS but are instead more intended
|
||||
to be compared against each other, across browsers, or across versions of
|
||||
wasm-bindgen.
|
||||
</p>
|
||||
|
||||
<table id='js-vs-wasm-bindgen'>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Benchmark</td>
|
||||
<td>Result</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id='wbindgen-body'>
|
||||
<tr>
|
||||
<td>
|
||||
Access <code>Node.nodeType</code> with <code>final</code>
|
||||
|
||||
<a class='about-open' href='#'>(?)</a>
|
||||
|
||||
<p class='about'>
|
||||
This is similar to the <code>Node.nodeType</code> benchmark above
|
||||
except that it uses the <code>final</code> attribute in
|
||||
wasm-bindgen.
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<td class='bm' id='wbindgen_call_first_child_final_n_times'></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Access <code>Node.nodeType</code> with <code>structural</code>
|
||||
|
||||
<a class='about-open' href='#'>(?)</a>
|
||||
|
||||
<p class='about'>
|
||||
This is similar to the <code>Node.nodeType</code> benchmark above
|
||||
except that it uses the <code>structural</code> attribute in
|
||||
wasm-bindgen.
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<td class='bm' id='wbindgen_call_first_child_structural_n_times'></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
Call a custom JS class <code>Foo.bar</code> method with
|
||||
<code>final</code>
|
||||
|
||||
<a class='about-open' href='#'>(?)</a>
|
||||
|
||||
<p class='about'>
|
||||
This is similar to the <code>Node.nodeType</code> benchmark above
|
||||
except that it's not calling a DOM method but rather a custom JS
|
||||
class's method.
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<td class='bm' id='wbindgen_call_foo_bar_final_n_times'></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Call a custom JS class <code>Foo.bar</code> method with
|
||||
<code>structural</code>
|
||||
|
||||
<a class='about-open' href='#'>(?)</a>
|
||||
|
||||
<p class='about'>
|
||||
This is similar to the <code>Node.nodeType</code> benchmark above
|
||||
except that it's not calling a DOM method but rather a custom JS
|
||||
class's method.
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<td class='bm' id='wbindgen_call_foo_bar_structural_n_times'></td>
|
||||
</tr>
|
||||
|
||||
<tr style='display:none' class='str-benchmark'>
|
||||
<td>
|
||||
Pass <span class='str'></span> to/from wasm-bindgen
|
||||
|
||||
<a class='about-open' href='#'>(?)</a>
|
||||
|
||||
<p class='about'>
|
||||
This benchmarks the overhead of passing strings to wasm and
|
||||
also receiving them from wasm.
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<td class='bm'></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
182
benchmarks/index.js
Normal file
182
benchmarks/index.js
Normal file
@ -0,0 +1,182 @@
|
||||
// Benchmarking framework that we're using
|
||||
import 'https://unpkg.com/lodash@4.17.11/lodash.js';
|
||||
import 'https://unpkg.com/benchmark@2.1.4/benchmark.js';
|
||||
|
||||
// Import lots of functions from JS/wasm, and rename everything to have the
|
||||
// namespace of where it's coming from.
|
||||
import wbindgen_init, {
|
||||
call_js_thunk_n_times as wbindgen_call_js_thunk_n_times,
|
||||
call_js_add_n_times as wbindgen_call_js_add_n_times,
|
||||
thunk as wbindgen_thunk,
|
||||
add as wbindgen_add,
|
||||
fibonacci as wbindgen_fibonacci,
|
||||
call_node_first_child_n_times as wbindgen_call_node_first_child_n_times,
|
||||
call_node_node_type_n_times as wbindgen_call_node_node_type_n_times,
|
||||
count_node_types as wbindgen_count_node_types,
|
||||
call_first_child_final_n_times as wbindgen_call_first_child_final_n_times,
|
||||
call_first_child_structural_n_times as wbindgen_call_first_child_structural_n_times,
|
||||
call_foo_bar_final_n_times as wbindgen_call_foo_bar_final_n_times,
|
||||
call_foo_bar_structural_n_times as wbindgen_call_foo_bar_structural_n_times,
|
||||
str_roundtrip as wbindgen_str_roundtrip,
|
||||
} from './pkg/wasm_bindgen_benchmark.js';
|
||||
import {
|
||||
call_js_thunk_n_times as js_call_js_thunk_n_times,
|
||||
call_js_add_n_times as js_call_js_add_n_times,
|
||||
thunk as js_thunk,
|
||||
add as js_add,
|
||||
fibonacci as js_fibonacci,
|
||||
call_node_first_child_n_times as js_call_node_first_child_n_times,
|
||||
call_node_node_type_n_times as js_call_node_node_type_n_times,
|
||||
count_node_types as js_count_node_types,
|
||||
} from './js-benchmarks.js';
|
||||
import * as globals from './globals.js';
|
||||
import { Lock } from './utils.js';
|
||||
|
||||
// These are set for `raw.wasm`, which we import and configure manually:
|
||||
let raw_call_js_thunk_n_times = null;
|
||||
let raw_call_js_add_n_times = null;
|
||||
let raw_thunk = null;
|
||||
let raw_add = null;
|
||||
|
||||
// Create a `Map` of all benchmarks that we're going to execute, where the map
|
||||
// is from a benchmark's name to a thunk to execute for the benchmark.
|
||||
function makeBenchmarks() {
|
||||
const benchmarks = new Map();
|
||||
|
||||
benchmarks.wbindgen_thunk = wbindgen_thunk;
|
||||
benchmarks.raw_thunk = raw_thunk;
|
||||
benchmarks.js_thunk = js_thunk;
|
||||
|
||||
benchmarks.wbindgen_fib_40 = () => wbindgen_fibonacci(40);
|
||||
benchmarks.js_fib_40 = () => js_fibonacci(40);
|
||||
|
||||
benchmarks.wbindgen_add = () => wbindgen_add(2, 3);
|
||||
benchmarks.raw_add = () => raw_add(2, 3);
|
||||
benchmarks.js_add = () => js_add(2, 3);
|
||||
|
||||
benchmarks.js_call_js_thunk_n_times = () => js_call_js_thunk_n_times(10000);
|
||||
benchmarks.raw_call_js_thunk_n_times = () => raw_call_js_thunk_n_times(10000);
|
||||
benchmarks.wbindgen_call_js_thunk_n_times = () => wbindgen_call_js_thunk_n_times(10000);
|
||||
|
||||
benchmarks.js_call_js_add_n_times = () => js_call_js_add_n_times(10000, 2, 3);
|
||||
benchmarks.raw_call_js_add_n_times = () => raw_call_js_add_n_times(10000, 2, 3);
|
||||
benchmarks.wbindgen_call_js_add_n_times = () => wbindgen_call_js_add_n_times(10000, 2, 3);
|
||||
|
||||
const list = [];
|
||||
for (let i = 0; i < 10; i++)
|
||||
list.push(document.body);
|
||||
benchmarks.wbindgen_call_node_first_child_n_times = () => wbindgen_call_node_first_child_n_times(1000, list);
|
||||
benchmarks.js_call_node_first_child_n_times = () => js_call_node_first_child_n_times(1000, list);
|
||||
benchmarks.wbindgen_call_node_node_type_n_times = () => wbindgen_call_node_node_type_n_times(1000, list);
|
||||
benchmarks.js_call_node_node_type_n_times = () => js_call_node_node_type_n_times(1000, list);
|
||||
|
||||
const body = document.body;
|
||||
benchmarks.wbindgen_count_node_types = () => wbindgen_count_node_types(body);
|
||||
benchmarks.js_count_node_types = () => js_count_node_types(body);
|
||||
|
||||
benchmarks.wbindgen_call_first_child_final_n_times = () => wbindgen_call_first_child_final_n_times(10000, body);
|
||||
benchmarks.wbindgen_call_first_child_structural_n_times = () => wbindgen_call_first_child_structural_n_times(10000, body);
|
||||
|
||||
const foo = new globals.Foo();
|
||||
benchmarks.wbindgen_call_foo_bar_final_n_times = () => wbindgen_call_foo_bar_final_n_times(10000, foo);
|
||||
benchmarks.wbindgen_call_foo_bar_structural_n_times = () => wbindgen_call_foo_bar_structural_n_times(10000, foo);
|
||||
|
||||
|
||||
const strings = {
|
||||
ascii_small: 'ja',
|
||||
ascii_medium: 'aym0566x',
|
||||
ascii_number: '505874924095815681',
|
||||
ascii_date: 'Sun Aug 31 00:29:15 +0000 2014',
|
||||
ascii_url: 'https://pbs.twimg.com/profile_images/497760886795153410/LDjAwR_y_normal.jpeg',
|
||||
ascii_link: '<a href="http://twitter.com/download/iphone" rel="nofollow">Twitter for iPhone</a>',
|
||||
unicode: '@aym0566x \n\n名前:前田あゆみ\n第一印象:なんか怖っ!\n今の印象:とりあえずキモい。噛み合わない\n好きなところ:ぶすでキモいとこ😋✨✨\n思い出:んーーー、ありすぎ😊❤️\nLINE交換できる?:あぁ……ごめん✋\nトプ画をみて:照れますがな😘✨\n一言:お前は一生もんのダチ💖'
|
||||
}
|
||||
const template = document.querySelector('tr.str-benchmark');
|
||||
template.remove();
|
||||
const tbody = document.querySelector('tbody#wbindgen-body');
|
||||
for (const bm in strings) {
|
||||
const s = strings[bm];
|
||||
const bm_name = `wbindgen_str_${bm}`;
|
||||
benchmarks[bm_name] = () => wbindgen_str_roundtrip(s);
|
||||
|
||||
const row = template.cloneNode(true);
|
||||
row.querySelector('.str').textContent = bm;
|
||||
row.querySelector('td.bm').id = bm_name;
|
||||
row.removeAttribute('style');
|
||||
tbody.appendChild(row);
|
||||
}
|
||||
|
||||
return benchmarks;
|
||||
}
|
||||
|
||||
// Set up the page and initialize all event handlers and such.
|
||||
function run() {
|
||||
const benchmarks = makeBenchmarks();
|
||||
|
||||
const benchmarkLock = new Lock();
|
||||
for (const td of document.querySelectorAll('td.bm')) {
|
||||
const bm = benchmarks[td.id];
|
||||
if (typeof bm !== 'function')
|
||||
throw new Error(`no benchmark registered for ${td.id}`);
|
||||
|
||||
const run = document.createElement('a');
|
||||
run.href = '#';
|
||||
run.innerText = '(run)';
|
||||
run.onclick = function() {
|
||||
benchmarkLock.withLock(async () => {
|
||||
await executeAndUpdate(td.id, bm, td);
|
||||
});
|
||||
run.remove();
|
||||
td.innerText = 'executing ...';
|
||||
return false;
|
||||
};
|
||||
td.appendChild(run);
|
||||
}
|
||||
|
||||
for (const a of document.querySelectorAll('.about-open')) {
|
||||
a.onclick = function() {
|
||||
a.nextElementSibling.style.display = 'block';
|
||||
a.remove();
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function executeAndUpdate(name, bm, td) {
|
||||
const result = await executeBenchmark(name, bm);
|
||||
console.log(result.target);
|
||||
const rme = Math.round(result.target.stats.rme * 100) / 100;
|
||||
td.innerText = `${Math.round(result.target.hz).toLocaleString()}/s ±${rme}%`;
|
||||
}
|
||||
|
||||
function executeBenchmark(name, bm) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const suite = new Benchmark.Suite();
|
||||
suite.add(name, bm);
|
||||
suite.on('cycle', resolve);
|
||||
suite.run({ async: true });
|
||||
});
|
||||
}
|
||||
|
||||
// Load wasm files and when they're done (plus the DOM) then we initialize
|
||||
// everything
|
||||
const wasms = [];
|
||||
wasms.push(wbindgen_init('./pkg/wasm_bindgen_benchmark_bg.wasm'));
|
||||
wasms.push(fetch('./raw.wasm')
|
||||
.then(r => r.arrayBuffer())
|
||||
.then(m => WebAssembly.instantiate(m, { './globals.js': globals }))
|
||||
.then(m => {
|
||||
raw_call_js_thunk_n_times = m.instance.exports.call_js_thunk_n_times;
|
||||
raw_call_js_add_n_times = m.instance.exports.call_js_add_n_times;
|
||||
raw_thunk = m.instance.exports.thunk;
|
||||
raw_add = m.instance.exports.add;
|
||||
}));
|
||||
|
||||
Promise.all(wasms)
|
||||
.then(() => {
|
||||
if (document.readyState === 'loading')
|
||||
document.addEventListener('DOMContentLoaded', run);
|
||||
else
|
||||
run();
|
||||
})
|
||||
.catch(console.error);
|
76
benchmarks/js-benchmarks.js
Normal file
76
benchmarks/js-benchmarks.js
Normal file
@ -0,0 +1,76 @@
|
||||
import { jsthunk, add as jsadd } from './globals.js';
|
||||
|
||||
export function fibonacci(n) {
|
||||
let a = 1;
|
||||
let b = 1;
|
||||
let tmp = 0;
|
||||
|
||||
while (n > 1) {
|
||||
tmp = b;
|
||||
b += a;
|
||||
a = tmp;
|
||||
--n;
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
export function thunk() {}
|
||||
|
||||
export function call_js_thunk_n_times(n) {
|
||||
for (var i = 0; i < n; i++) {
|
||||
jsthunk();
|
||||
}
|
||||
}
|
||||
|
||||
export function add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
export function call_js_add_n_times(n, a, b) {
|
||||
for (var i = 0; i < n; i++) {
|
||||
jsadd(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
export function call_node_first_child_n_times(n, array_of_elements) {
|
||||
for (let i = 0; i < n; i++) {
|
||||
for (const element of array_of_elements)
|
||||
if (element.firstChild === null)
|
||||
throw new Error("bad");
|
||||
}
|
||||
}
|
||||
|
||||
export function call_node_node_type_n_times(n, array_of_elements) {
|
||||
for (let i = 0; i < n; i++) {
|
||||
for (const element of array_of_elements)
|
||||
if (element.nodeType === 100)
|
||||
throw new Error("bad");
|
||||
}
|
||||
}
|
||||
|
||||
export function call_node_has_child_nodes_n_times(n, array_of_elements) {
|
||||
for (let i = 0; i < n; i++) {
|
||||
for (const element of array_of_elements)
|
||||
if (!element.hasChildNodes())
|
||||
throw new Error("bad");
|
||||
}
|
||||
}
|
||||
|
||||
export function count_node_types(element) {
|
||||
const types = [];
|
||||
|
||||
function count(node, types) {
|
||||
while(node) {
|
||||
const type = node.nodeType;
|
||||
while (types.length <= type)
|
||||
types.push(0);
|
||||
types[type] += 1;
|
||||
count(node.firstChild, types);
|
||||
node = node.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
count(element, types);
|
||||
return types;
|
||||
}
|
BIN
benchmarks/raw.wasm
Normal file
BIN
benchmarks/raw.wasm
Normal file
Binary file not shown.
52
benchmarks/raw.wast
Normal file
52
benchmarks/raw.wast
Normal file
@ -0,0 +1,52 @@
|
||||
(module
|
||||
(import "./globals.js" "jsthunk" (func $js_thunk))
|
||||
(import "./globals.js" "add" (func $js_add (param i32) (param i32) (result i32)))
|
||||
|
||||
(export "call_js_thunk_n_times" (func $call_thunk))
|
||||
(export "call_js_add_n_times" (func $call_add))
|
||||
(export "thunk" (func $thunk))
|
||||
(export "add" (func $add))
|
||||
|
||||
(func $call_thunk (param i32)
|
||||
block
|
||||
get_local 0
|
||||
i32.eqz
|
||||
br_if 0
|
||||
loop
|
||||
call $js_thunk
|
||||
get_local 0
|
||||
i32.const 1
|
||||
i32.sub
|
||||
tee_local 0
|
||||
br_if 0
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
(func $call_add (param i32) (param i32) (param i32)
|
||||
block
|
||||
get_local 0
|
||||
i32.eqz
|
||||
br_if 0
|
||||
loop
|
||||
get_local 2
|
||||
get_local 1
|
||||
call $js_add
|
||||
drop
|
||||
get_local 0
|
||||
i32.const 1
|
||||
i32.sub
|
||||
tee_local 0
|
||||
br_if 0
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
(func $thunk)
|
||||
|
||||
(func $add (param i32) (param i32) (result i32)
|
||||
get_local 0
|
||||
get_local 1
|
||||
i32.add
|
||||
)
|
||||
)
|
179
benchmarks/src/lib.rs
Normal file
179
benchmarks/src/lib.rs
Normal file
@ -0,0 +1,179 @@
|
||||
extern crate wasm_bindgen;
|
||||
extern crate web_sys;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::Node;
|
||||
|
||||
#[wasm_bindgen(raw_module = "../globals.js")]
|
||||
extern {
|
||||
#[wasm_bindgen(js_name = jsthunk)]
|
||||
fn js_thunk();
|
||||
#[wasm_bindgen(js_name = add)]
|
||||
fn js_add(a: i32, b: i32) -> i32;
|
||||
|
||||
pub type Foo;
|
||||
#[wasm_bindgen(method, final, js_name = bar)]
|
||||
fn bar_final(this: &Foo);
|
||||
#[wasm_bindgen(method, structural, js_name = bar)]
|
||||
fn bar_structural(this: &Foo);
|
||||
|
||||
#[wasm_bindgen(js_name = jsthunk)]
|
||||
fn doesnt_throw();
|
||||
#[wasm_bindgen(catch, js_name = jsthunk)]
|
||||
fn doesnt_throw_catch() -> Result<(), JsValue>;
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn call_js_thunk_n_times(n: usize) {
|
||||
for _ in 0..n {
|
||||
js_thunk();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn call_js_add_n_times(n: usize, a: i32, b: i32) {
|
||||
for _ in 0..n {
|
||||
js_add(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn thunk() {}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn add(a: i32, b: i32) -> i32 {
|
||||
a + b
|
||||
}
|
||||
|
||||
static mut FIB_HIGH: i32 = 0;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn fibonacci(n: i32) -> i32 {
|
||||
let mut a = 1u64;
|
||||
let mut b = 1;
|
||||
for _ in 0..n {
|
||||
let tmp = b;
|
||||
b += a;
|
||||
a = tmp;
|
||||
}
|
||||
unsafe { FIB_HIGH = (a >> 32) as i32; }
|
||||
return a as i32
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn fibonacci_high() -> i32 {
|
||||
unsafe { FIB_HIGH }
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn call_foo_bar_final_n_times(n: usize, foo: &Foo) {
|
||||
for _ in 0..n {
|
||||
foo.bar_final();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn call_foo_bar_structural_n_times(n: usize, foo: &Foo) {
|
||||
for _ in 0..n {
|
||||
foo.bar_structural();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn call_doesnt_throw_n_times(n: usize) {
|
||||
for _ in 0..n {
|
||||
doesnt_throw();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn call_doesnt_throw_with_catch_n_times(n: usize) {
|
||||
for _ in 0..n {
|
||||
if let Err(e) = doesnt_throw_catch() {
|
||||
wasm_bindgen::throw_val(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern {
|
||||
pub type Element;
|
||||
|
||||
#[wasm_bindgen(method, js_name = firstChild, final, getter)]
|
||||
fn first_child_final(this: &Element) -> Element;
|
||||
#[wasm_bindgen(method, js_name = firstChild, structural, getter)]
|
||||
fn first_child_structural(this: &Element) -> Element;
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn call_first_child_final_n_times(n: usize, element: &Element) {
|
||||
for _ in 0..n {
|
||||
drop(element.first_child_final());
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn call_first_child_structural_n_times(n: usize, element: &Element) {
|
||||
for _ in 0..n {
|
||||
drop(element.first_child_structural());
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn call_node_first_child_n_times(n: usize, elements: Vec<JsValue>) {
|
||||
for _ in 0..n {
|
||||
for element in elements.iter() {
|
||||
let element = element.unchecked_ref::<Node>();
|
||||
assert!(element.first_child().is_some());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn call_node_node_type_n_times(n: usize, elements: Vec<JsValue>) {
|
||||
for _ in 0..n {
|
||||
for element in elements.iter() {
|
||||
let element = element.unchecked_ref::<Node>();
|
||||
assert!(element.node_type() != 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn call_node_has_child_nodes_n_times(n: usize, elements: Vec<JsValue>) {
|
||||
for _ in 0..n {
|
||||
for element in elements.iter() {
|
||||
let element = element.unchecked_ref::<Node>();
|
||||
assert!(element.has_child_nodes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn count_node_types(element: Node) {
|
||||
let mut count = Vec::new();
|
||||
count_node_types(element, &mut count);
|
||||
|
||||
fn count_node_types(mut element: Node, count: &mut Vec<u32>) {
|
||||
loop {
|
||||
let t = element.node_type();
|
||||
if t as usize >= count.len() {
|
||||
count.resize(t as usize + 1, 0);
|
||||
}
|
||||
count[t as usize] += 1;
|
||||
if let Some(s) = element.first_child() {
|
||||
count_node_types(s, count);
|
||||
}
|
||||
match element.next_sibling() {
|
||||
Some(s) => element = s,
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn str_roundtrip(s: String) -> String {
|
||||
s
|
||||
}
|
14
benchmarks/utils.js
Normal file
14
benchmarks/utils.js
Normal file
@ -0,0 +1,14 @@
|
||||
export class Lock {
|
||||
constructor() {
|
||||
this.lockHolder = null;
|
||||
}
|
||||
|
||||
async withLock(scope) {
|
||||
while (this.lockHolder !== null) {
|
||||
await this.lockHolder;
|
||||
}
|
||||
this.lockHolder = Promise.resolve(null).then(scope);
|
||||
await this.lockHolder;
|
||||
this.lockHolder = null;
|
||||
}
|
||||
}
|
8
ci/azure-install-wasm-pack.yml
Normal file
8
ci/azure-install-wasm-pack.yml
Normal file
@ -0,0 +1,8 @@
|
||||
steps:
|
||||
- script: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
|
||||
displayName: "install wasm-pack"
|
||||
- script: |
|
||||
set -ex
|
||||
cargo build -p wasm-bindgen-cli
|
||||
ln -snf `pwd`/target/debug/wasm-bindgen $HOME/.cargo/bin/wasm-bindgen
|
||||
displayName: "install wasm-bindgen for `wasm-pack` to use"
|
Loading…
Reference in New Issue
Block a user