Web Worker performance optimization is crucial for ensuring efficient application operation. Here are optimization strategies across multiple aspects.
1. Worker Creation and Destruction Optimization
Reuse Worker Instances
javascript// ❌ Frequent creation and destruction (poor performance) function processTask(data) { const worker = new Worker('worker.js'); worker.postMessage(data); worker.onmessage = function(e) { console.log(e.data); worker.terminate(); }; } // ✅ Reuse Worker (good performance) const worker = new Worker('worker.js'); const pendingTasks = []; function processTask(data) { return new Promise((resolve) => { const taskId = Date.now(); pendingTasks[taskId] = resolve; worker.postMessage({ taskId, data }); }); } worker.onmessage = function(e) { const { taskId, result } = e.data; if (pendingTasks[taskId]) { pendingTasks[taskId](result); delete pendingTasks[taskId]; } };
Worker Pool Pattern
javascriptclass WorkerPool { constructor(workerPath, poolSize = 4) { this.workerPath = workerPath; this.poolSize = poolSize; this.workers = []; this.taskQueue = []; this.init(); } init() { for (let i = 0; i < this.poolSize; i++) { const worker = new Worker(this.workerPath); worker.onmessage = (e) => this.handleMessage(e, worker); this.workers.push({ worker, busy: false }); } } execute(data) { return new Promise((resolve) => { const availableWorker = this.workers.find(w => !w.busy); if (availableWorker) { availableWorker.busy = true; availableWorker.worker.postMessage({ data, resolve }); } else { this.taskQueue.push({ data, resolve }); } }); } handleMessage(event, workerObj) { const { result } = event.data; const pendingTask = workerObj.worker.pendingTask; if (pendingTask) { pendingTask.resolve(result); workerObj.worker.pendingTask = null; } workerObj.busy = false; if (this.taskQueue.length > 0) { const nextTask = this.taskQueue.shift(); workerObj.busy = true; workerObj.worker.pendingTask = nextTask; workerObj.worker.postMessage({ data: nextTask.data }); } } } // Use Worker pool const pool = new WorkerPool('worker.js', 4); pool.execute(largeData).then(result => console.log(result));
2. Message Passing Optimization
Use Transferable Objects
javascript// ❌ Deep copy (poor performance) const buffer = new ArrayBuffer(1024 * 1024); worker.postMessage({ buffer }); // Copy 1MB of data // ✅ Transfer ownership (good performance) const buffer = new ArrayBuffer(1024 * 1024); worker.postMessage({ buffer }, [buffer]); // Zero copy // buffer is now empty, ownership has been transferred
Batch Process Messages
javascript// ❌ Frequently send small messages for (let i = 0; i < 10000; i++) { worker.postMessage({ index: i, value: data[i] }); } // ✅ Batch send worker.postMessage({ data: data.slice(0, 10000) });
Use SharedArrayBuffer (requires specific headers)
javascript// Server needs to set COOP/COEP headers // Cross-Origin-Opener-Policy: same-origin // Cross-Origin-Embedder-Policy: require-corp const sharedBuffer = new SharedArrayBuffer(1024); const sharedArray = new Int32Array(sharedBuffer); worker.postMessage({ sharedBuffer }, [sharedBuffer]); // Main thread and Worker can simultaneously access sharedArray sharedArray[0] = 42;
3. Data Processing Optimization
Chunk Process Large Data
javascript// worker.js self.onmessage = function(e) { const { data, chunkSize } = e.data; const results = []; for (let i = 0; i < data.length; i += chunkSize) { const chunk = data.slice(i, i + chunkSize); const result = processChunk(chunk); results.push(result); // Periodically report progress if (i % (chunkSize * 10) === 0) { self.postMessage({ type: 'progress', progress: i / data.length }); } } self.postMessage({ type: 'complete', results }); };
Use WebAssembly to Accelerate Computation
javascript// worker.js const wasmModule = await WebAssembly.instantiateStreaming( fetch('compute.wasm') ); self.onmessage = function(e) { const { data } = e.data; const result = wasmModule.instance.exports.compute(data); self.postMessage(result); };
4. Memory Management Optimization
Release Resources Timely
javascript// Main thread const worker = new Worker('worker.js'); // Terminate Worker after use worker.terminate(); // Inside Worker self.onmessage = function(e) { const result = process(e.data); self.postMessage(result); // Clean up large objects e.data = null; };
Avoid Memory Leaks
javascript// ❌ May cause memory leak const worker = new Worker('worker.js'); worker.onmessage = function(e) { // Closure references large object const largeData = e.data; setTimeout(() => { console.log(largeData); }, 10000); }; // ✅ Release reference timely const worker = new Worker('worker.js'); worker.onmessage = function(e) { const result = process(e.data); console.log(result); e.data = null; // Release reference };
5. Error Handling and Monitoring
Error Handling
javascriptconst worker = new Worker('worker.js'); worker.onerror = function(event) { console.error('Worker error:', event.message); console.error('Line:', event.lineno); console.error('File:', event.filename); // Decide whether to restart Worker based on error type if (isRecoverable(event.error)) { restartWorker(); } }; worker.onmessageerror = function(event) { console.error('Message error:', event.data); };
Performance Monitoring
javascriptclass WorkerMonitor { constructor(worker) { this.worker = worker; this.messageCount = 0; this.totalTime = 0; this.startTime = null; this.setupMonitoring(); } setupMonitoring() { this.worker.onmessage = (e) => { if (this.startTime) { const duration = performance.now() - this.startTime; this.totalTime += duration; this.messageCount++; console.log(`Message ${this.messageCount}: ${duration.toFixed(2)}ms`); console.log(`Average: ${(this.totalTime / this.messageCount).toFixed(2)}ms`); } }; } sendMessage(data) { this.startTime = performance.now(); this.worker.postMessage(data); } getStats() { return { messageCount: this.messageCount, totalTime: this.totalTime, averageTime: this.messageCount > 0 ? this.totalTime / this.messageCount : 0 }; } } // Use monitoring const monitor = new WorkerMonitor(worker); monitor.sendMessage(data);
6. Debugging Tips
Use console.log
javascript// worker.js self.onmessage = function(e) { console.log('[Worker] Received:', e.data); const result = process(e.data); console.log('[Worker] Result:', result); self.postMessage(result); };
Use Chrome DevTools
- Open Chrome DevTools
- Switch to "Sources" panel
- Find Worker script on the left
- Set breakpoints for debugging
Use postMessage for Debugging
javascript// Main thread worker.postMessage({ type: 'debug', data: { key: 'value' } }); // Worker self.onmessage = function(e) { if (e.data.type === 'debug') { console.log('[Worker Debug]', e.data.data); } };
Best Practices Summary
- Reuse Workers: Avoid frequent creation and destruction
- Use Worker Pool: Manage multiple Worker instances
- Transferable Objects: Use transfer instead of copy for large data
- Batch Processing: Reduce message passing frequency
- Chunk Processing: Process large data in chunks, report progress periodically
- Release Resources Timely: Terminate Worker after use
- Error Handling: Add comprehensive error handling mechanisms
- Performance Monitoring: Monitor Worker performance metrics
- WebAssembly: Use WASM for compute-intensive tasks
- SharedArrayBuffer: Use when shared memory is needed (note security restrictions)