5月27日 14:02
How to optimize Web Worker performance?
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)