The pending question
Is Rust/Wasm fast enough to even bother when comparing to Javascript? has now yet another biased benchmark. I've decided to take one of my
Julia/Mandelbrot demos, benchmark it "as-is" and then, port to Rust and compare.
The HTML/Javascript source is short enough to post it here, the original demo code has some additional features (zooming) which are not present here
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mandelbrot</title>
<script type="text/javascript">
var cx = 0, cy = 0, kat1 = 0, kat2 = 1;
var WJulia = 1024;
var HJulia = 1024;
var frame = 0;
var startDate;
var contextJulia;
var contextSmall;
var pixJulia;
var imgdJulia;
var iStart;
var iEnd;
var jStart;
var jEnd;
var iDelta;
var jDelta;
function setInitialScale()
{
iStart = -1.92;
iEnd = 1.92;
jStart = -1.92;
jEnd = 1.92;
iDelta = (iEnd-iStart)/1024;
jDelta = (jEnd-jStart)/1024;
startDate = new Date();
}
function SetupMandel()
{
var elemJulia = document.getElementById('JuliaCanvas');
if (elemJulia && elemJulia.getContext)
{
contextJulia = elemJulia.getContext('2d');
if (contextJulia)
{
imgdJulia = contextJulia.createImageData(WJulia, HJulia);
pixJulia = imgdJulia.data;
}
}
setInitialScale();
/* start */
requestAnimationFrame( LoopMandel );
}
function LoopMandel()
{
kat1 += 0.0021;
kat2 += 0.0039;
cx = .681 * Math.sin(kat1);
cy = .626 * Math.cos(kat2);
frame++;
document.getElementById('fps').innerHTML = `FPS: ${1000*frame/(new Date()-startDate)}`;
RysujMandel();
requestAnimationFrame( LoopMandel );
}
/* tworzenie bitowego obrazu */
function RysujMandel()
{
/* obliczenia */
var wi, wj;
var i, j;
var iterations = 255;
var px = 0;
for (i = iStart, wi = 0; wi < 1024; i += iDelta, wi++)
{
var py = 0;
for (j = jStart, wj = 0; wj < 1024; j += jDelta, wj++)
{
var c = 0;
var x = cx;
var y = cy;
while (((x*x + y*y) < 4) && (c < iterations))
{
[x, y] = [x * x - y * y + i, 2 * x * y + j];
c++;
}
SetPixelColor( pixJulia, (py * WJulia + px) << 2, 255, 255-c, 255-c, 255 - (c/2) );
py++;
}
px++;
}
/* kopiowanie bitowego obrazu do context/canvas */
contextJulia.putImageData(imgdJulia, 0, 0);
}
function SetPixelColor(pix,offs, a, r, g, b)
{
pix[offs++] = r;
pix[offs++] = g;
pix[offs++] = b;
pix[offs] = a;
}
window.addEventListener( 'load', SetupMandel );
</script>
</head>
<body>
<span id="fps" style='display:block'></span>
<canvas id="JuliaCanvas" width="1024" height="1024">
</canvas>
</body>
</html>
When run on my machine and the Edge Browser, it makes ~10-11 frames per second. Not bad assuming 1024x1024 image and 255 iterations.
Same code ported to Rust and compiled with wasm-pack
// https://rustwasm.github.io/wasm-bindgen/examples/dom.html
use wasm_bindgen::prelude::*;
use wasm_bindgen::Clamped;
use web_sys::{CanvasRenderingContext2d, ImageData};
/* tworzenie bitowego obrazu */
#[wasm_bindgen]
pub fn mandel(
ctx: &CanvasRenderingContext2d,
width: u32, height: u32,
cx: f64, cy: f64) -> Result<(), JsValue> {
let i_start = -1.92;
let i_end = 1.92;
let j_start = -1.92;
let j_end = 1.92;
let i_delta = (i_end-i_start)/width as f64;
let j_delta = (j_end-j_start)/height as f64;
/* obliczenia */
let iterations = 255;
let mut pix_julia = Vec::with_capacity( usize::try_from(width * height).unwrap() );
let mut j = j_start;
for _wj in 0..height {
let mut i = i_start;
for _wi in 0..width {
let mut c: u8 = 0;
let mut x = cx;
let mut y = cy;
while ( (x*x + y*y) < 4.0) && (c < iterations)
{
let _tx = x;
x = x * x - y * y + i;
y = 2.0 * _tx * y + j;
c += 1;
}
pix_julia.push( 255-c );
pix_julia.push( 255-c );
pix_julia.push( 255-(c/2) );
pix_julia.push( 255 );
i += i_delta;
}
j += j_delta;
}
let data = ImageData::new_with_u8_clamped_array_and_sh(Clamped(&pix_julia), width, height)?;
ctx.put_image_data(&data, 0.0, 0.0)
}
The cargo.tomlwas
[package]
name = "hello-wasm"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.87"
[dependencies.web-sys]
version = "0.3.4"
features = [
'Document',
'Element',
'HtmlElement',
'Node',
'Window',
'ImageData',
'CanvasRenderingContext2d'
]
[profile.release]
opt-level = 3
lto = true
[package.metadata.wasm-pack.profile.release]
wasm-opt = ["-O4", "--enable-mutable-globals"]
and the HTML
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>Mandelbrot</title>
</head>
<body>
<script type="module">
import init, { mandel } from "./pkg/hello_wasm.js";
(async function() {
await init();
SetupMandel();
})();
var cx = 0, cy = 0, kat1 = 0, kat2 = 1;
var WJulia = 1024;
var HJulia = 1024;
var frame = 0;
var startDate;
var contextJulia;
var contextSmall;
var pixJulia;
var imgdJulia;
function SetupMandel()
{
var elemJulia = document.getElementById('JuliaCanvas');
if (elemJulia && elemJulia.getContext)
{
contextJulia = elemJulia.getContext('2d');
if (contextJulia)
{
imgdJulia = contextJulia.createImageData(WJulia, HJulia);
pixJulia = imgdJulia.data;
}
}
startDate = new Date();
/* start */
requestAnimationFrame( LoopMandel );
}
function LoopMandel()
{
kat1 += 0.0021;
kat2 += 0.0039;
cx = .681 * Math.sin(kat1);
cy = .626 * Math.cos(kat2);
frame++;
document.getElementById('fps').innerHTML = `FPS: ${1000*frame/(new Date()-startDate)}`;
mandel(contextJulia, WJulia, HJulia, cx, cy);
requestAnimationFrame( LoopMandel );
}
</script>
<span id="fps" style='display: block'></span>
<canvas id="JuliaCanvas" width="1024" height="1024">
</canvas>
</body>
</html>
And the result? Similar, it also makes 10-11 frames per second. Additional tests (different resolution and number of iterations) and possible optimizations can be applied here.