Back to all articles
Featured image for article: WebAssembly for High-Performance Web Applications
WebAssembly
25 min read521 views

WebAssembly for High-Performance Web Applications

Learn how to use WebAssembly to bring near-native performance to web applications with Rust and C++.

#WebAssembly#Rust#Performance#C++#SIMD

WebAssembly for High-Performance Web Applications

Introduction

WebAssembly (Wasm) is a binary instruction format that enables high-performance applications on the web. It allows code written in languages like Rust, C++, and C to run in the browser at near-native speed.

Setting Up WebAssembly Development

Rust Setup for WebAssembly

bash
1# Install Rust 2curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 3 4# Add WebAssembly target 5rustup target add wasm32-unknown-unknown 6 7# Install wasm-bindgen 8cargo install wasm-bindgen-cli

Basic Rust WebAssembly Module

rust
1// src/lib.rs 2use wasm_bindgen::prelude::*; 3 4#[wasm_bindgen] 5extern "C" { 6 #[wasm_bindgen(js_namespace = console)] 7 fn log(s: &str); 8} 9 10#[wasm_bindgen] 11pub fn greet(name: &str) { 12 log(&format!("Hello, {}!", name)); 13} 14 15#[wasm_bindgen] 16pub struct ImageProcessor { 17 width: u32, 18 height: u32, 19 pixels: Vec<u8>, 20} 21 22#[wasm_bindgen] 23impl ImageProcessor { 24 #[wasm_bindgen(constructor)] 25 pub fn new(width: u32, height: u32) -> ImageProcessor { 26 let pixels = vec![0; (width * height * 4) as usize]; 27 ImageProcessor { 28 width, 29 height, 30 pixels, 31 } 32 } 33 34 pub fn get_pixels(&self) -> Vec<u8> { 35 self.pixels.clone() 36 } 37 38 pub fn apply_grayscale(&mut self) { 39 for i in (0..self.pixels.len()).step_by(4) { 40 let r = self.pixels[i] as f32; 41 let g = self.pixels[i + 1] as f32; 42 let b = self.pixels[i + 2] as f32; 43 44 // Calculate grayscale 45 let gray = (0.299 * r + 0.587 * g + 0.114 * b) as u8; 46 47 self.pixels[i] = gray; 48 self.pixels[i + 1] = gray; 49 self.pixels[i + 2] = gray; 50 // Alpha channel remains unchanged 51 } 52 } 53 54 pub fn apply_blur(&mut self, radius: u32) { 55 let width = self.width as usize; 56 let height = self.height as usize; 57 let radius = radius as usize; 58 59 // Create a copy for reading 60 let original = self.pixels.clone(); 61 62 for y in 0..height { 63 for x in 0..width { 64 let mut r_sum = 0u32; 65 let mut g_sum = 0u32; 66 let mut b_sum = 0u32; 67 let mut count = 0u32; 68 69 // Sample neighboring pixels 70 for dy in (-(radius as i32))..=(radius as i32) { 71 for dx in (-(radius as i32))..=(radius as i32) { 72 let nx = x as i32 + dx; 73 let ny = y as i32 + dy; 74 75 if nx >= 0 && nx < width as i32 && ny >= 0 && ny < height as i32 { 76 let idx = ((ny * width as i32 + nx) * 4) as usize; 77 r_sum += original[idx] as u32; 78 g_sum += original[idx + 1] as u32; 79 b_sum += original[idx + 2] as u32; 80 count += 1; 81 } 82 } 83 } 84 85 // Calculate averages 86 let idx = (y * width + x) * 4; 87 self.pixels[idx] = (r_sum / count) as u8; 88 self.pixels[idx + 1] = (g_sum / count) as u8; 89 self.pixels[idx + 2] = (b_sum / count) as u8; 90 } 91 } 92 } 93} 94 95#[wasm_bindgen] 96pub fn fibonacci(n: u32) -> u32 { 97 if n <= 1 { 98 return n; 99 } 100 101 let mut a = 0; 102 let mut b = 1; 103 104 for _ in 2..=n { 105 let c = a + b; 106 a = b; 107 b = c; 108 } 109 110 b 111} 112 113#[wasm_bindgen] 114pub fn matrix_multiply(a: &[f32], b: &[f32], size: usize) -> Vec<f32> { 115 let mut result = vec![0.0; size * size]; 116 117 for i in 0..size { 118 for j in 0..size { 119 let mut sum = 0.0; 120 for k in 0..size { 121 sum += a[i * size + k] * b[k * size + j]; 122 } 123 result[i * size + j] = sum; 124 } 125 } 126 127 result 128}

Build Configuration

toml
1# Cargo.toml 2[package] 3name = "wasm-image-processor" 4version = "0.1.0" 5edition = "2021" 6 7[lib] 8crate-type = ["cdylib"] 9 10[dependencies] 11wasm-bindgen = "0.2" 12 13[profile.release] 14lto = true 15opt-level = "s" 16codegen-units = 1

JavaScript Integration

javascript
1// index.js 2import init, { ImageProcessor, fibonacci, matrix_multiply } from './pkg/wasm_image_processor.js'; 3 4async function run() { 5 // Initialize WebAssembly module 6 await init(); 7 8 // Example 1: Fibonacci calculation 9 console.log('Fibonacci(20):', fibonacci(20)); 10 11 // Example 2: Matrix multiplication 12 const size = 100; 13 const a = new Float32Array(size * size).fill(1.0); 14 const b = new Float32Array(size * size).fill(2.0); 15 16 console.time('WebAssembly Matrix Multiply'); 17 const result = matrix_multiply(a, b, size); 18 console.timeEnd('WebAssembly Matrix Multiply'); 19 20 // Example 3: Image processing 21 const canvas = document.getElementById('canvas'); 22 const ctx = canvas.getContext('2d'); 23 24 // Load image 25 const img = new Image(); 26 img.onload = async () => { 27 canvas.width = img.width; 28 canvas.height = img.height; 29 ctx.drawImage(img, 0, 0); 30 31 // Get image data 32 const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 33 34 // Create WebAssembly image processor 35 const processor = new ImageProcessor(canvas.width, canvas.height); 36 37 // Copy pixel data 38 const wasmPixels = processor.get_pixels(); 39 wasmPixels.set(imageData.data); 40 41 // Apply grayscale 42 console.time('WebAssembly Grayscale'); 43 processor.apply_grayscale(); 44 console.timeEnd('WebAssembly Grayscale'); 45 46 // Apply blur 47 console.time('WebAssembly Blur'); 48 processor.apply_blur(5); 49 console.timeEnd('WebAssembly Blur'); 50 51 // Update canvas with processed image 52 const processedPixels = processor.get_pixels(); 53 imageData.data.set(processedPixels); 54 ctx.putImageData(imageData, 0, 0); 55 }; 56 57 img.src = 'image.jpg'; 58} 59 60// Run when page loads 61if (document.readyState === 'loading') { 62 document.addEventListener('DOMContentLoaded', run); 63} else { 64 run(); 65}

Webpack Configuration

javascript
1// webpack.config.js 2const path = require('path'); 3const HtmlWebpackPlugin = require('html-webpack-plugin'); 4const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); 5 6module.exports = { 7 entry: './src/index.js', 8 output: { 9 path: path.resolve(__dirname, 'dist'), 10 filename: 'bundle.js', 11 }, 12 experiments: { 13 asyncWebAssembly: true, 14 }, 15 plugins: [ 16 new HtmlWebpackPlugin({ 17 template: './src/index.html', 18 }), 19 new WasmPackPlugin({ 20 crateDirectory: path.resolve(__dirname, 'crate'), 21 extraArgs: '--target web', 22 outDir: path.resolve(__dirname, 'src/pkg'), 23 }), 24 ], 25 module: { 26 rules: [ 27 { 28 test: /\.wasm$/, 29 type: 'webassembly/async', 30 }, 31 ], 32 }, 33 devServer: { 34 static: { 35 directory: path.join(__dirname, 'dist'), 36 }, 37 compress: true, 38 port: 3000, 39 }, 40};

Performance Comparison

javascript
1// performance-test.js 2async function runPerformanceTests() { 3 await init(); 4 5 // Test 1: Fibonacci (JavaScript vs WebAssembly) 6 console.log('\n=== Fibonacci Performance ==='); 7 8 function jsFibonacci(n) { 9 if (n <= 1) return n; 10 let a = 0, b = 1; 11 for (let i = 2; i <= n; i++) { 12 let c = a + b; 13 a = b; 14 b = c; 15 } 16 return b; 17 } 18 19 const n = 1000000; 20 21 console.time('JavaScript Fibonacci'); 22 const jsResult = jsFibonacci(40); 23 console.timeEnd('JavaScript Fibonacci'); 24 25 console.time('WebAssembly Fibonacci'); 26 const wasmResult = fibonacci(40); 27 console.timeEnd('WebAssembly Fibonacci'); 28 29 console.log(`Results match: ${jsResult === wasmResult}`); 30 31 // Test 2: Matrix Multiplication 32 console.log('\n=== Matrix Multiplication Performance ==='); 33 34 const size = 200; 35 const a = new Float32Array(size * size); 36 const b = new Float32Array(size * size); 37 38 for (let i = 0; i < a.length; i++) { 39 a[i] = Math.random(); 40 b[i] = Math.random(); 41 } 42 43 function jsMatrixMultiply(a, b, size) { 44 const result = new Float32Array(size * size); 45 46 for (let i = 0; i < size; i++) { 47 for (let j = 0; j < size; j++) { 48 let sum = 0; 49 for (let k = 0; k < size; k++) { 50 sum += a[i * size + k] * b[k * size + j]; 51 } 52 result[i * size + j] = sum; 53 } 54 } 55 56 return result; 57 } 58 59 console.time('JavaScript Matrix Multiply'); 60 const jsMatrixResult = jsMatrixMultiply(a, b, size); 61 console.timeEnd('JavaScript Matrix Multiply'); 62 63 console.time('WebAssembly Matrix Multiply'); 64 const wasmMatrixResult = matrix_multiply(a, b, size); 65 console.timeEnd('WebAssembly Matrix Multiply'); 66 67 // Verify results 68 let matches = true; 69 for (let i = 0; i < 10; i++) { 70 if (Math.abs(jsMatrixResult[i] - wasmMatrixResult[i]) > 0.0001) { 71 matches = false; 72 break; 73 } 74 } 75 console.log(`Results match: ${matches}`); 76}

Advanced WebAssembly Features

Multi-threading with Web Workers

rust
1// src/worker.rs 2use wasm_bindgen::prelude::*; 3use rayon::prelude::*; 4 5#[wasm_bindgen] 6extern "C" { 7 #[wasm_bindgen(js_namespace = self)] 8 fn postMessage(msg: JsValue); 9} 10 11#[wasm_bindgen] 12pub fn process_in_parallel(data: &[f32]) -> Vec<f32> { 13 data.par_iter() 14 .map(|&x| { 15 // Simulate CPU-intensive work 16 let mut result = x; 17 for _ in 0..1000 { 18 result = result.sin().cos(); 19 } 20 result 21 }) 22 .collect() 23} 24 25#[wasm_bindgen] 26pub fn start_worker() { 27 // Listen for messages from main thread 28 let closure = Closure::wrap(Box::new(move |msg: JsValue| { 29 if let Ok(data) = serde_wasm_bindgen::from_value::<Vec<f32>>(msg) { 30 let result = process_in_parallel(&data); 31 let result_js = serde_wasm_bindgen::to_value(&result).unwrap(); 32 postMessage(result_js); 33 } 34 }) as Box<dyn FnMut(JsValue)>); 35 36 // Set up message listener 37 let _ = js_sys::global() 38 .unchecked_into::<web_sys::DedicatedWorkerGlobalScope>() 39 .set_onmessage(Some(closure.as_ref().unchecked_ref())); 40 41 closure.forget(); 42}

SIMD Operations

rust
1// src/simd.rs 2use std::arch::wasm32::*; 3use wasm_bindgen::prelude::*; 4 5#[wasm_bindgen] 6pub fn simd_add(a: &[f32], b: &[f32]) -> Vec<f32> { 7 assert_eq!(a.len(), b.len()); 8 assert!(a.len() % 4 == 0, "Input length must be multiple of 4"); 9 10 let mut result = vec![0.0; a.len()]; 11 12 for i in (0..a.len()).step_by(4) { 13 let va = v128_load(a.as_ptr().add(i) as *const v128); 14 let vb = v128_load(b.as_ptr().add(i) as *const v128); 15 let vc = f32x4_add(va, vb); 16 v128_store(result.as_mut_ptr().add(i) as *mut v128, vc); 17 } 18 19 result 20} 21 22#[wasm_bindgen] 23pub fn simd_dot_product(a: &[f32], b: &[f32]) -> f32 { 24 assert_eq!(a.len(), b.len()); 25 26 let mut sum = f32x4_splat(0.0); 27 28 for i in (0..a.len()).step_by(4) { 29 let va = v128_load(a.as_ptr().add(i) as *const v128); 30 let vb = v128_load(b.as_ptr().add(i) as *const v128); 31 sum = f32x4_add(sum, f32x4_mul(va, vb)); 32 } 33 34 // Horizontal add 35 let sum_arr: [f32; 4] = unsafe { std::mem::transmute(sum) }; 36 sum_arr.iter().sum() 37}

Integration with React

jsx
1// components/ImageProcessor.jsx 2import React, { useRef, useState, useEffect } from 'react'; 3import init, { ImageProcessor } from '../wasm/image-processor'; 4 5function ImageProcessorComponent() { 6 const canvasRef = useRef(null); 7 const [processor, setProcessor] = useState(null); 8 const [loading, setLoading] = useState(true); 9 10 useEffect(() => { 11 async function loadWasm() { 12 await init(); 13 setLoading(false); 14 } 15 loadWasm(); 16 }, []); 17 18 const handleImageUpload = async (event) => { 19 const file = event.target.files[0]; 20 if (!file) return; 21 22 const reader = new FileReader(); 23 reader.onload = async (e) => { 24 const img = new Image(); 25 img.onload = async () => { 26 const canvas = canvasRef.current; 27 const ctx = canvas.getContext('2d'); 28 29 canvas.width = img.width; 30 canvas.height = img.height; 31 ctx.drawImage(img, 0, 0); 32 33 const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 34 const wasmProcessor = new ImageProcessor(canvas.width, canvas.height); 35 36 const wasmPixels = wasmProcessor.get_pixels(); 37 wasmPixels.set(imageData.data); 38 39 setProcessor(wasmProcessor); 40 }; 41 img.src = e.target.result; 42 }; 43 reader.readAsDataURL(file); 44 }; 45 46 const applyGrayscale = () => { 47 if (!processor) return; 48 49 const canvas = canvasRef.current; 50 const ctx = canvas.getContext('2d'); 51 52 processor.apply_grayscale(); 53 54 const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 55 const processedPixels = processor.get_pixels(); 56 imageData.data.set(processedPixels); 57 ctx.putImageData(imageData, 0, 0); 58 }; 59 60 const applyBlur = () => { 61 if (!processor) return; 62 63 const canvas = canvasRef.current; 64 const ctx = canvas.getContext('2d'); 65 66 processor.apply_blur(5); 67 68 const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 69 const processedPixels = processor.get_pixels(); 70 imageData.data.set(processedPixels); 71 ctx.putImageData(imageData, 0, 0); 72 }; 73 74 if (loading) { 75 return <div>Loading WebAssembly module...</div>; 76 } 77 78 return ( 79 <div className="image-processor"> 80 <input 81 type="file" 82 accept="image/*" 83 onChange={handleImageUpload} 84 /> 85 <canvas ref={canvasRef} style={{ maxWidth: '100%' }} /> 86 <div className="controls"> 87 <button onClick={applyGrayscale}>Grayscale</button> 88 <button onClick={applyBlur}>Blur</button> 89 </div> 90 </div> 91 ); 92} 93 94export default ImageProcessorComponent;

Deployment and Optimization

bash
1# Build optimized WebAssembly 2RUSTFLAGS='-C target-feature=+simd,+bulk-memory,+atomics' \ 3 wasm-pack build --target web --release 4 5# Optimize binary size 6wasm-opt -O3 -o optimized.wasm pkg/wasm_image_processor_bg.wasm 7 8# Compress with gzip 9gzip -k -9 optimized.wasm 10 11# Brotli compression 12brotli -k -9 optimized.wasm

Service Worker for Caching

javascript
1// sw.js 2const CACHE_NAME = 'wasm-cache-v1'; 3const WASM_FILES = [ 4 '/optimized.wasm', 5 '/pkg/wasm_image_processor.js', 6 '/pkg/wasm_image_processor_bg.wasm', 7]; 8 9self.addEventListener('install', (event) => { 10 event.waitUntil( 11 caches.open(CACHE_NAME).then((cache) => { 12 return cache.addAll(WASM_FILES); 13 }) 14 ); 15}); 16 17self.addEventListener('fetch', (event) => { 18 if (WASM_FILES.some(url => event.request.url.includes(url))) { 19 event.respondWith( 20 caches.match(event.request).then((response) => { 21 return response || fetch(event.request); 22 }) 23 ); 24 } 25});
Profile picture of Sumit Kumar Pandey

Sumit Kumar Pandey

Full-Stack Developer

Full-Stack Developer with 5+ years of experience building scalable web applications. Passionate about clean code, performance optimization, and modern web technologies.

About the Author

Author information for Sumit Kumar Pandey

Share this article

Found this helpful? Share with your network!

0 shares

Discussion (0)

Share your thoughts and join the conversation

Leave a comment

Be respectful and stay on topic

Write your comment in the text area above. Comments should be respectful and relevant to the article.

AI Chat Assistant

Interactive AI assistant for Sumit Kumar Pandey's portfolio website. Ask questions about technical skills, work experience, projects, availability, and contact information. Powered by Next.js API.