UNPKG

fast-md5-web

Version:

A TypeScript project with tsup bundler for Rust WASM MD5 calculation

494 lines (445 loc) 18.3 kB
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>MD5 计算性能对比测试</title> <style> body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; background-color: #f5f5f5; } .container { background: white; border-radius: 10px; padding: 30px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } h1 { color: #333; text-align: center; margin-bottom: 30px; } .file-input-section { margin-bottom: 30px; padding: 20px; border: 2px dashed #ddd; border-radius: 8px; text-align: center; } .file-input { margin: 10px 0; } .test-controls { display: flex; gap: 15px; margin-bottom: 30px; flex-wrap: wrap; } .btn { padding: 12px 24px; border: none; border-radius: 6px; cursor: pointer; font-size: 16px; transition: all 0.3s; } .btn-primary { background-color: #007bff; color: white; } .btn-primary:hover { background-color: #0056b3; } .btn-success { background-color: #28a745; color: white; } .btn-success:hover { background-color: #1e7e34; } .btn:disabled { background-color: #6c757d; cursor: not-allowed; } .results { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px; } .result-card { border: 1px solid #ddd; border-radius: 8px; padding: 20px; background-color: #f8f9fa; } .result-card h3 { margin-top: 0; color: #495057; } .metric { display: flex; justify-content: space-between; margin: 10px 0; padding: 8px; background: white; border-radius: 4px; } .metric-label { font-weight: bold; } .metric-value { color: #007bff; } .progress { width: 100%; height: 20px; background-color: #e9ecef; border-radius: 10px; overflow: hidden; margin: 10px 0; } .progress-bar { height: 100%; background-color: #007bff; transition: width 0.3s; } .file-info { background: #e3f2fd; padding: 15px; border-radius: 6px; margin: 15px 0; } .error { color: #dc3545; background: #f8d7da; padding: 10px; border-radius: 4px; margin: 10px 0; } .success { color: #155724; background: #d4edda; padding: 10px; border-radius: 4px; margin: 10px 0; } .comparison { margin-top: 20px; padding: 20px; background: #fff3cd; border-radius: 8px; border-left: 4px solid #ffc107; } @media (max-width: 768px) { .results { grid-template-columns: 1fr; } .test-controls { flex-direction: column; } } </style> </head> <body> <div class="container"> <h1>🚀 MD5 计算性能对比测试</h1> <div class="file-input-section"> <h3>选择测试文件</h3> <input type="file" id="fileInput" class="file-input" accept="*/*"> <p>支持任意类型文件,建议选择较大文件以便观察性能差异</p> </div> <div class="test-controls"> <button id="testWasm" class="btn btn-primary" disabled>测试 WASM MD5</button> <button id="testSpark" class="btn btn-success" disabled>测试 Spark MD5</button> <button id="testBoth" class="btn btn-primary" disabled>同时测试对比</button> <button id="clearResults" class="btn" style="background-color: #6c757d; color: white;">清空结果</button> </div> <div id="fileInfo"></div> <div id="progress" style="display: none;"> <div class="progress"> <div id="progressBar" class="progress-bar" style="width: 0%"></div> </div> <div id="progressText">准备中...</div> </div> <div class="results" id="results" style="display: none;"> <div class="result-card"> <h3>🦀 WASM MD5 (Rust)</h3> <div id="wasmResults"></div> </div> <div class="result-card"> <h3>⚡ Spark MD5 (JavaScript)</h3> <div id="sparkResults"></div> </div> </div> <div id="comparison" class="comparison" style="display: none;"></div> <div id="messages"></div> </div> <!-- 引入 spark-md5 库 --> <script src="https://cdn.jsdelivr.net/npm/spark-md5@3.0.2/spark-md5.min.js"></script> <!-- 引入 WASM MD5 模块 --> <script type="module"> import init, { Md5Calculator } from './wasm_md5.js'; let wasmInitialized = false; let md5Calculator = null; let selectedFile = null; // 初始化 WASM async function initWasm() { try { // 使用新的异步初始化方式 await init('./wasm_md5_bg.wasm'); md5Calculator = new Md5Calculator(); md5Calculator.set_log_enabled(true); wasmInitialized = true; showMessage('WASM 模块初始化成功', 'success'); } catch (error) { showMessage(`WASM 初始化失败: ${error.message}`, 'error'); console.error('WASM 初始化详细错误:', error); } } // 显示消息 function showMessage(message, type = 'info') { const messagesDiv = document.getElementById('messages'); const messageDiv = document.createElement('div'); messageDiv.className = type; messageDiv.textContent = message; messagesDiv.appendChild(messageDiv); // 3秒后自动移除消息 setTimeout(() => { if (messageDiv.parentNode) { messageDiv.parentNode.removeChild(messageDiv); } }, 3000); } // 格式化文件大小 function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } // 格式化时间 function formatTime(ms) { if (ms < 1000) return `${ms.toFixed(2)} ms`; return `${(ms / 1000).toFixed(2)} s`; } // 更新进度 function updateProgress(percent, text) { const progressDiv = document.getElementById('progress'); const progressBar = document.getElementById('progressBar'); const progressText = document.getElementById('progressText'); progressDiv.style.display = 'block'; progressBar.style.width = `${percent}%`; progressText.textContent = text; } // 隐藏进度 function hideProgress() { document.getElementById('progress').style.display = 'none'; } // 读取文件为 ArrayBuffer function readFileAsArrayBuffer(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = () => reject(reader.error); reader.readAsArrayBuffer(file); }); } // 使用 WASM 计算 MD5 async function calculateWasmMd5(file) { if (!wasmInitialized) { throw new Error('WASM 模块未初始化'); } updateProgress(10, '读取文件数据...'); const arrayBuffer = await readFileAsArrayBuffer(file); const uint8Array = new Uint8Array(arrayBuffer); updateProgress(30, '使用 WASM 计算 MD5...'); const startTime = performance.now(); const md5Hash = await md5Calculator.calculate_md5_async(uint8Array, 32); const endTime = performance.now(); const duration = endTime - startTime; updateProgress(100, 'WASM MD5 计算完成'); return { hash: md5Hash, duration: duration, throughput: (file.size / 1024 / 1024) / (duration / 1000) // MB/s }; } // 使用 Spark MD5 计算 async function calculateSparkMd5(file) { updateProgress(10, '读取文件数据...'); const arrayBuffer = await readFileAsArrayBuffer(file); updateProgress(30, '使用 Spark MD5 计算...'); const startTime = performance.now(); const spark = new SparkMD5.ArrayBuffer(); spark.append(arrayBuffer); const md5Hash = spark.end(); const endTime = performance.now(); const duration = endTime - startTime; updateProgress(100, 'Spark MD5 计算完成'); return { hash: md5Hash, duration: duration, throughput: (file.size / 1024 / 1024) / (duration / 1000) // MB/s }; } // 显示结果 function displayResults(wasmResult, sparkResult) { const resultsDiv = document.getElementById('results'); const wasmResultsDiv = document.getElementById('wasmResults'); const sparkResultsDiv = document.getElementById('sparkResults'); const comparisonDiv = document.getElementById('comparison'); resultsDiv.style.display = 'grid'; // WASM 结果 if (wasmResult) { wasmResultsDiv.innerHTML = ` <div class="metric"> <span class="metric-label">MD5 哈希:</span> <span class="metric-value" style="font-family: monospace; font-size: 12px;">${wasmResult.hash}</span> </div> <div class="metric"> <span class="metric-label">计算时间:</span> <span class="metric-value">${formatTime(wasmResult.duration)}</span> </div> <div class="metric"> <span class="metric-label">吞吐量:</span> <span class="metric-value">${wasmResult.throughput.toFixed(2)} MB/s</span> </div> `; } // Spark 结果 if (sparkResult) { sparkResultsDiv.innerHTML = ` <div class="metric"> <span class="metric-label">MD5 哈希:</span> <span class="metric-value" style="font-family: monospace; font-size: 12px;">${sparkResult.hash}</span> </div> <div class="metric"> <span class="metric-label">计算时间:</span> <span class="metric-value">${formatTime(sparkResult.duration)}</span> </div> <div class="metric"> <span class="metric-label">吞吐量:</span> <span class="metric-value">${sparkResult.throughput.toFixed(2)} MB/s</span> </div> `; } // 对比结果 if (wasmResult && sparkResult) { const speedRatio = sparkResult.duration / wasmResult.duration; const winner = wasmResult.duration < sparkResult.duration ? 'WASM' : 'Spark'; const hashMatch = wasmResult.hash.toLowerCase() === sparkResult.hash.toLowerCase(); comparisonDiv.style.display = 'block'; comparisonDiv.innerHTML = ` <h3>📊 性能对比结果</h3> <div class="metric"> <span class="metric-label">哈希值一致性:</span> <span class="metric-value" style="color: ${hashMatch ? '#28a745' : '#dc3545'}"> ${hashMatch ? '✅ 一致' : '❌ 不一致'} </span> </div> <div class="metric"> <span class="metric-label">性能优胜者:</span> <span class="metric-value">${winner} (快 ${Math.abs(speedRatio - 1).toFixed(2)}x)</span> </div> <div class="metric"> <span class="metric-label">WASM vs Spark:</span> <span class="metric-value">${formatTime(wasmResult.duration)} vs ${formatTime(sparkResult.duration)}</span> </div> `; } } // 事件监听器 document.getElementById('fileInput').addEventListener('change', function(e) { selectedFile = e.target.files[0]; if (selectedFile) { const fileInfoDiv = document.getElementById('fileInfo'); fileInfoDiv.innerHTML = ` <div class="file-info"> <h4>📁 文件信息</h4> <div class="metric"> <span class="metric-label">文件名:</span> <span class="metric-value">${selectedFile.name}</span> </div> <div class="metric"> <span class="metric-label">文件大小:</span> <span class="metric-value">${formatFileSize(selectedFile.size)}</span> </div> <div class="metric"> <span class="metric-label">文件类型:</span> <span class="metric-value">${selectedFile.type || '未知'}</span> </div> </div> `; // 启用测试按钮 document.getElementById('testWasm').disabled = !wasmInitialized; document.getElementById('testSpark').disabled = false; document.getElementById('testBoth').disabled = !wasmInitialized; } }); document.getElementById('testWasm').addEventListener('click', async function() { if (!selectedFile) return; try { this.disabled = true; const result = await calculateWasmMd5(selectedFile); displayResults(result, null); showMessage('WASM MD5 计算完成', 'success'); } catch (error) { showMessage(`WASM MD5 计算失败: ${error.message}`, 'error'); } finally { this.disabled = false; hideProgress(); } }); document.getElementById('testSpark').addEventListener('click', async function() { if (!selectedFile) return; try { this.disabled = true; const result = await calculateSparkMd5(selectedFile); displayResults(null, result); showMessage('Spark MD5 计算完成', 'success'); } catch (error) { showMessage(`Spark MD5 计算失败: ${error.message}`, 'error'); } finally { this.disabled = false; hideProgress(); } }); document.getElementById('testBoth').addEventListener('click', async function() { if (!selectedFile) return; try { this.disabled = true; // 先测试 WASM updateProgress(0, '开始 WASM MD5 测试...'); const wasmResult = await calculateWasmMd5(selectedFile); // 再测试 Spark updateProgress(50, '开始 Spark MD5 测试...'); const sparkResult = await calculateSparkMd5(selectedFile); updateProgress(100, '所有测试完成'); displayResults(wasmResult, sparkResult); showMessage('性能对比测试完成', 'success'); } catch (error) { showMessage(`对比测试失败: ${error.message}`, 'error'); } finally { this.disabled = false; hideProgress(); } }); document.getElementById('clearResults').addEventListener('click', function() { document.getElementById('results').style.display = 'none'; document.getElementById('comparison').style.display = 'none'; document.getElementById('wasmResults').innerHTML = ''; document.getElementById('sparkResults').innerHTML = ''; document.getElementById('messages').innerHTML = ''; hideProgress(); }); // 初始化 initWasm(); </script> </body> </html>