fast-md5-web
Version:
A TypeScript project with tsup bundler for Rust WASM MD5 calculation
494 lines (445 loc) • 18.3 kB
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>