572 lines
17 KiB
HTML
572 lines
17 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Markdown Worker 性能测试 - Phase 5.1.2</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||
background: #f5f5f5;
|
||
padding: 20px;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.header {
|
||
background: white;
|
||
padding: 24px;
|
||
border-radius: 8px;
|
||
margin-bottom: 20px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
h1 {
|
||
margin-bottom: 12px;
|
||
color: #333;
|
||
}
|
||
|
||
.info-box {
|
||
background: #e3f2fd;
|
||
border: 1px solid #2196F3;
|
||
border-radius: 4px;
|
||
padding: 16px;
|
||
margin: 16px 0;
|
||
}
|
||
|
||
.controls {
|
||
display: flex;
|
||
gap: 12px;
|
||
margin: 16px 0;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
button {
|
||
padding: 10px 20px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: #4CAF50;
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: #45a049;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: #2196F3;
|
||
color: white;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: #0b7dda;
|
||
}
|
||
|
||
.btn-warning {
|
||
background: #ff9800;
|
||
color: white;
|
||
}
|
||
|
||
.btn-warning:hover {
|
||
background: #e68900;
|
||
}
|
||
|
||
.test-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.test-panel {
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.test-panel h2 {
|
||
font-size: 18px;
|
||
margin-bottom: 12px;
|
||
color: #333;
|
||
}
|
||
|
||
.metric {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 10px;
|
||
margin: 6px 0;
|
||
background: #f8f9fa;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.metric.highlight {
|
||
background: #e8f5e9;
|
||
border-left: 4px solid #4CAF50;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.progress {
|
||
margin: 12px 0;
|
||
}
|
||
|
||
.progress-bar {
|
||
height: 6px;
|
||
background: #e0e0e0;
|
||
border-radius: 3px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.progress-fill {
|
||
height: 100%;
|
||
background: #4CAF50;
|
||
transition: width 0.3s;
|
||
}
|
||
|
||
.progress-text {
|
||
font-size: 13px;
|
||
color: #666;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.results {
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.comparison-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.comparison-table th,
|
||
.comparison-table td {
|
||
padding: 12px;
|
||
text-align: left;
|
||
border: 1px solid #e0e0e0;
|
||
}
|
||
|
||
.comparison-table th {
|
||
background: #f5f5f5;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.winner {
|
||
background: #e8f5e9 !important;
|
||
font-weight: bold;
|
||
color: #2e7d32;
|
||
}
|
||
|
||
.console-output {
|
||
background: #1e1e1e;
|
||
color: #d4d4d4;
|
||
padding: 16px;
|
||
border-radius: 4px;
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 13px;
|
||
line-height: 1.6;
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
white-space: pre-wrap;
|
||
}
|
||
|
||
.console-output .info { color: #4fc3f7; }
|
||
.console-output .success { color: #81c784; }
|
||
.console-output .warning { color: #ffb74d; }
|
||
.console-output .error { color: #e57373; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>🚀 Phase 5.1.2 - Markdown Worker 性能测试</h1>
|
||
<p>对比 Web Worker 多线程解析 vs. 主线程解析的性能差异</p>
|
||
|
||
<div class="info-box">
|
||
<strong>测试说明:</strong>
|
||
<ul style="margin: 8px 0 0 20px;">
|
||
<li>测试不同长度的 Markdown 文本解析性能</li>
|
||
<li>对比 Worker 池和主线程的性能指标</li>
|
||
<li>验证流式更新场景的流畅度</li>
|
||
<li>查看控制台获取详细日志</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="controls">
|
||
<button class="btn-primary" onclick="runAllTests()">🔬 运行所有测试</button>
|
||
<button class="btn-secondary" onclick="runShortTextTest()">测试短文本 (500字)</button>
|
||
<button class="btn-secondary" onclick="runMediumTextTest()">测试中文本 (5000字)</button>
|
||
<button class="btn-secondary" onclick="runLongTextTest()">测试长文本 (20000字)</button>
|
||
<button class="btn-warning" onclick="viewStats()">📊 查看统计</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="test-grid">
|
||
<div class="test-panel">
|
||
<h2>🔵 Web Worker 模式</h2>
|
||
<div id="worker-status" class="metric">状态:等待测试</div>
|
||
<div class="progress">
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" id="worker-progress" style="width: 0%"></div>
|
||
</div>
|
||
<div class="progress-text" id="worker-progress-text">...</div>
|
||
</div>
|
||
<div id="worker-metrics"></div>
|
||
</div>
|
||
|
||
<div class="test-panel">
|
||
<h2>🟠 主线程模式</h2>
|
||
<div id="main-status" class="metric">状态:等待测试</div>
|
||
<div class="progress">
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" id="main-progress" style="width: 0%"></div>
|
||
</div>
|
||
<div class="progress-text" id="main-progress-text">...</div>
|
||
</div>
|
||
<div id="main-metrics"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="results">
|
||
<h2>📊 对比结果</h2>
|
||
<div id="comparison-results">
|
||
<p style="color: #666;">运行测试后将显示对比结果...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="results">
|
||
<h2>📝 测试日志</h2>
|
||
<div id="console-log" class="console-output">
|
||
等待测试...
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 加载依赖 -->
|
||
<script src="https://gcore.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||
<script src="../../js/chatbot/config/performance-config.js"></script>
|
||
<script src="../../js/chatbot/utils/performance-monitor.js"></script>
|
||
<script src="../../js/workers/markdown-worker-pool.js"></script>
|
||
|
||
<script>
|
||
// 测试数据生成
|
||
function generateMarkdown(length) {
|
||
const paragraphs = Math.ceil(length / 100);
|
||
let md = '# 性能测试文档\n\n';
|
||
|
||
for (let i = 0; i < paragraphs; i++) {
|
||
md += `## 章节 ${i + 1}\n\n`;
|
||
md += `这是第 ${i + 1} 个段落。`.repeat(5) + '\n\n';
|
||
|
||
if (i % 3 === 0) {
|
||
md += '```javascript\n';
|
||
md += 'function test() {\n';
|
||
md += ' return "Hello World";\n';
|
||
md += '}\n';
|
||
md += '```\n\n';
|
||
}
|
||
|
||
if (i % 5 === 0) {
|
||
md += '- 列表项 1\n';
|
||
md += '- 列表项 2\n';
|
||
md += '- 列表项 3\n\n';
|
||
}
|
||
}
|
||
|
||
return md;
|
||
}
|
||
|
||
// 测试结果存储
|
||
const testResults = {
|
||
worker: [],
|
||
mainThread: []
|
||
};
|
||
|
||
// 日志函数
|
||
function log(message, type = 'info') {
|
||
const logEl = document.getElementById('console-log');
|
||
const timestamp = new Date().toLocaleTimeString();
|
||
const className = type;
|
||
logEl.innerHTML += `<span class="${className}">[${timestamp}] ${message}</span>\n`;
|
||
logEl.scrollTop = logEl.scrollHeight;
|
||
console.log(`[${type.toUpperCase()}] ${message}`);
|
||
}
|
||
|
||
// 更新进度
|
||
function updateProgress(mode, percent, text) {
|
||
document.getElementById(`${mode}-progress`).style.width = percent + '%';
|
||
document.getElementById(`${mode}-progress-text`).textContent = text;
|
||
}
|
||
|
||
// 更新状态
|
||
function updateStatus(mode, text) {
|
||
document.getElementById(`${mode}-status`).textContent = text;
|
||
}
|
||
|
||
// 更新指标
|
||
function updateMetrics(mode, metrics) {
|
||
const metricsEl = document.getElementById(`${mode}-metrics`);
|
||
metricsEl.innerHTML = `
|
||
<div class="metric"><span>平均耗时:</span><span>${metrics.avgTime.toFixed(2)} ms</span></div>
|
||
<div class="metric"><span>最小耗时:</span><span>${metrics.minTime.toFixed(2)} ms</span></div>
|
||
<div class="metric"><span>最大耗时:</span><span>${metrics.maxTime.toFixed(2)} ms</span></div>
|
||
<div class="metric"><span>总耗时:</span><span>${metrics.totalTime.toFixed(2)} ms</span></div>
|
||
`;
|
||
}
|
||
|
||
// 测试 Worker 模式
|
||
async function testWorkerMode(markdown, rounds = 10) {
|
||
log('开始测试 Worker 模式...', 'info');
|
||
updateStatus('worker', '状态:测试中...');
|
||
|
||
const times = [];
|
||
|
||
for (let i = 0; i < rounds; i++) {
|
||
const start = performance.now();
|
||
|
||
try {
|
||
await window.markdownWorkerPool.parse(markdown);
|
||
const duration = performance.now() - start;
|
||
times.push(duration);
|
||
|
||
updateProgress('worker', ((i + 1) / rounds) * 100, `${i + 1}/${rounds}`);
|
||
} catch (error) {
|
||
log(`Worker 测试失败: ${error.message}`, 'error');
|
||
times.push(NaN);
|
||
}
|
||
}
|
||
|
||
const validTimes = times.filter(t => !isNaN(t));
|
||
const metrics = {
|
||
avgTime: validTimes.reduce((a, b) => a + b, 0) / validTimes.length,
|
||
minTime: Math.min(...validTimes),
|
||
maxTime: Math.max(...validTimes),
|
||
totalTime: validTimes.reduce((a, b) => a + b, 0)
|
||
};
|
||
|
||
updateStatus('worker', '状态:完成 ✅');
|
||
updateMetrics('worker', metrics);
|
||
log(`Worker 模式完成: 平均 ${metrics.avgTime.toFixed(2)} ms`, 'success');
|
||
|
||
return metrics;
|
||
}
|
||
|
||
// 测试主线程模式
|
||
async function testMainThreadMode(markdown, rounds = 10) {
|
||
log('开始测试主线程模式...', 'info');
|
||
updateStatus('main', '状态:测试中...');
|
||
|
||
const times = [];
|
||
|
||
for (let i = 0; i < rounds; i++) {
|
||
const start = performance.now();
|
||
|
||
try {
|
||
marked.parse(markdown);
|
||
const duration = performance.now() - start;
|
||
times.push(duration);
|
||
|
||
updateProgress('main', ((i + 1) / rounds) * 100, `${i + 1}/${rounds}`);
|
||
} catch (error) {
|
||
log(`主线程测试失败: ${error.message}`, 'error');
|
||
times.push(NaN);
|
||
}
|
||
}
|
||
|
||
const validTimes = times.filter(t => !isNaN(t));
|
||
const metrics = {
|
||
avgTime: validTimes.reduce((a, b) => a + b, 0) / validTimes.length,
|
||
minTime: Math.min(...validTimes),
|
||
maxTime: Math.max(...validTimes),
|
||
totalTime: validTimes.reduce((a, b) => a + b, 0)
|
||
};
|
||
|
||
updateStatus('main', '状态:完成 ✅');
|
||
updateMetrics('main', metrics);
|
||
log(`主线程模式完成: 平均 ${metrics.avgTime.toFixed(2)} ms`, 'success');
|
||
|
||
return metrics;
|
||
}
|
||
|
||
// 显示对比结果
|
||
function displayComparison(workerMetrics, mainMetrics, testName) {
|
||
const improvement = ((mainMetrics.avgTime - workerMetrics.avgTime) / mainMetrics.avgTime * 100);
|
||
const faster = workerMetrics.avgTime < mainMetrics.avgTime ? 'worker' : 'main';
|
||
|
||
const html = `
|
||
<h3>${testName} - 性能对比</h3>
|
||
<table class="comparison-table">
|
||
<thead>
|
||
<tr>
|
||
<th>指标</th>
|
||
<th>Web Worker</th>
|
||
<th>主线程</th>
|
||
<th>改善</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>平均耗时</td>
|
||
<td class="${faster === 'worker' ? 'winner' : ''}">${workerMetrics.avgTime.toFixed(2)} ms</td>
|
||
<td class="${faster === 'main' ? 'winner' : ''}">${mainMetrics.avgTime.toFixed(2)} ms</td>
|
||
<td>${improvement > 0 ? '↓' : '↑'} ${Math.abs(improvement).toFixed(1)}%</td>
|
||
</tr>
|
||
<tr>
|
||
<td>最小耗时</td>
|
||
<td>${workerMetrics.minTime.toFixed(2)} ms</td>
|
||
<td>${mainMetrics.minTime.toFixed(2)} ms</td>
|
||
<td>-</td>
|
||
</tr>
|
||
<tr>
|
||
<td>最大耗时</td>
|
||
<td>${workerMetrics.maxTime.toFixed(2)} ms</td>
|
||
<td>${mainMetrics.maxTime.toFixed(2)} ms</td>
|
||
<td>-</td>
|
||
</tr>
|
||
<tr>
|
||
<td>总耗时</td>
|
||
<td>${workerMetrics.totalTime.toFixed(0)} ms</td>
|
||
<td>${mainMetrics.totalTime.toFixed(0)} ms</td>
|
||
<td>${improvement > 0 ? '↓' : '↑'} ${Math.abs(improvement).toFixed(1)}%</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<div class="info-box" style="margin-top: 16px; background: ${improvement > 0 ? '#e8f5e9' : '#fff3cd'};">
|
||
<strong>结论:</strong>
|
||
${improvement > 0
|
||
? `✅ <strong>Web Worker 性能提升 ${improvement.toFixed(1)}%</strong>,主线程阻塞时间显著减少`
|
||
: `⚠️ 对于短文本,通信开销可能超过计算开销,Worker 性能反而稍差`
|
||
}
|
||
</div>
|
||
`;
|
||
|
||
document.getElementById('comparison-results').innerHTML = html;
|
||
}
|
||
|
||
// 单独测试函数
|
||
async function runShortTextTest() {
|
||
document.getElementById('console-log').innerHTML = '';
|
||
log('========================================', 'info');
|
||
log('开始测试:短文本 (500字)', 'info');
|
||
log('========================================', 'info');
|
||
|
||
const markdown = generateMarkdown(500);
|
||
log(`生成了 ${markdown.length} 字符的测试文本`, 'info');
|
||
|
||
const workerMetrics = await testWorkerMode(markdown, 20);
|
||
await new Promise(resolve => setTimeout(resolve, 500));
|
||
const mainMetrics = await testMainThreadMode(markdown, 20);
|
||
|
||
displayComparison(workerMetrics, mainMetrics, '短文本测试');
|
||
}
|
||
|
||
async function runMediumTextTest() {
|
||
document.getElementById('console-log').innerHTML = '';
|
||
log('========================================', 'info');
|
||
log('开始测试:中文本 (5000字)', 'info');
|
||
log('========================================', 'info');
|
||
|
||
const markdown = generateMarkdown(5000);
|
||
log(`生成了 ${markdown.length} 字符的测试文本`, 'info');
|
||
|
||
const workerMetrics = await testWorkerMode(markdown, 20);
|
||
await new Promise(resolve => setTimeout(resolve, 500));
|
||
const mainMetrics = await testMainThreadMode(markdown, 20);
|
||
|
||
displayComparison(workerMetrics, mainMetrics, '中文本测试');
|
||
}
|
||
|
||
async function runLongTextTest() {
|
||
document.getElementById('console-log').innerHTML = '';
|
||
log('========================================', 'info');
|
||
log('开始测试:长文本 (20000字)', 'info');
|
||
log('========================================', 'info');
|
||
|
||
const markdown = generateMarkdown(20000);
|
||
log(`生成了 ${markdown.length} 字符的测试文本`, 'info');
|
||
|
||
const workerMetrics = await testWorkerMode(markdown, 10);
|
||
await new Promise(resolve => setTimeout(resolve, 500));
|
||
const mainMetrics = await testMainThreadMode(markdown, 10);
|
||
|
||
displayComparison(workerMetrics, mainMetrics, '长文本测试');
|
||
}
|
||
|
||
// 运行所有测试
|
||
async function runAllTests() {
|
||
await runShortTextTest();
|
||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||
await runMediumTextTest();
|
||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||
await runLongTextTest();
|
||
|
||
log('========================================', 'success');
|
||
log('所有测试完成!', 'success');
|
||
log('========================================', 'success');
|
||
}
|
||
|
||
// 查看统计
|
||
function viewStats() {
|
||
const stats = window.markdownWorkerPool.getStats();
|
||
console.log('Worker Pool 统计:', stats);
|
||
|
||
alert(`Worker Pool 统计信息:
|
||
|
||
池大小: ${stats.poolSize}
|
||
初始化: ${stats.initialized ? '是' : '否'}
|
||
可用: ${stats.available ? '是' : '否'}
|
||
|
||
已完成任务: ${stats.tasksCompleted}
|
||
失败任务: ${stats.tasksFailed}
|
||
降级任务: ${stats.tasksFallback}
|
||
队列长度: ${stats.queueLength}
|
||
待处理: ${stats.pendingTasks}
|
||
|
||
平均 Worker 耗时: ${stats.avgWorkerTime.toFixed(2)} ms
|
||
平均主线程耗时: ${stats.avgMainThreadTime.toFixed(2)} ms
|
||
|
||
详细信息已输出到控制台`);
|
||
}
|
||
|
||
// 页面加载完成
|
||
window.addEventListener('load', function() {
|
||
console.log('%c═══════════════════════════════════════', 'color: #4CAF50; font-weight: bold');
|
||
console.log('%c Phase 5.1.2 Markdown Worker 测试', 'color: #4CAF50; font-weight: bold');
|
||
console.log('%c═══════════════════════════════════════', 'color: #4CAF50; font-weight: bold');
|
||
console.log('');
|
||
console.log('Worker 池状态:', window.markdownWorkerPool ? '已初始化' : '未初始化');
|
||
console.log('');
|
||
console.log('使用方法:');
|
||
console.log(' 1. 点击"运行所有测试"进行完整测试');
|
||
console.log(' 2. 或选择单独的测试场景');
|
||
console.log(' 3. 查看对比结果和性能指标');
|
||
console.log('');
|
||
|
||
log('页面加载完成,准备就绪', 'success');
|
||
log(`Worker 池已初始化,大小: ${window.markdownWorkerPool.config.poolSize}`, 'info');
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|