437 lines
13 KiB
HTML
437 lines
13 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>KaTeX 缓存性能测试 - Phase 4.2+</title>
|
||
<link rel="stylesheet" href="https://gcore.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
|
||
<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; }
|
||
|
||
.warning-box {
|
||
background: #fff3cd;
|
||
border: 2px solid #ffc107;
|
||
border-radius: 6px;
|
||
padding: 16px;
|
||
margin: 16px 0;
|
||
}
|
||
|
||
.success-box {
|
||
background: #d4edda;
|
||
border: 2px solid #28a745;
|
||
border-radius: 6px;
|
||
padding: 16px;
|
||
margin: 16px 0;
|
||
}
|
||
|
||
.success-box h3 {
|
||
color: #155724;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.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; }
|
||
|
||
.comparison {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.test-card {
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.test-card h3 {
|
||
margin-bottom: 12px;
|
||
color: #333;
|
||
}
|
||
|
||
.metric {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 10px 12px;
|
||
margin: 8px 0;
|
||
background: #f8f9fa;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.metric.highlight {
|
||
background: #e8f5e9;
|
||
border-left: 4px solid #4CAF50;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.metric.warning {
|
||
background: #ffebee;
|
||
border-left: 4px solid #f44336;
|
||
}
|
||
|
||
.huge-improvement {
|
||
font-size: 32px;
|
||
font-weight: bold;
|
||
color: #2e7d32;
|
||
text-align: center;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.formulas-preview {
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
margin-bottom: 20px;
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.console-log {
|
||
background: #1e1e1e;
|
||
color: #d4d4d4;
|
||
padding: 16px;
|
||
border-radius: 4px;
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 13px;
|
||
line-height: 1.6;
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
white-space: pre-wrap;
|
||
}
|
||
|
||
.console-log .success { color: #81c784; }
|
||
.console-log .warning { color: #ffb74d; }
|
||
.console-log .info { color: #4fc3f7; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>🚀 Phase 4.2+ KaTeX 公式缓存性能测试</h1>
|
||
<p>测试缓存对公式渲染性能的影响(针对 4.6s 阻塞问题)</p>
|
||
|
||
<div class="warning-box">
|
||
<h3>⚠️ 测试场景</h3>
|
||
<p><strong>问题:</strong> 打开充满公式的 chatbot 时,KaTeX 渲染阻塞主线程 4.6 秒</p>
|
||
<p><strong>优化:</strong> 使用 LRU 缓存避免重复公式的重新渲染</p>
|
||
<p><strong>预期收益:</strong> 缓存命中时渲染时间减少 99%(从 50ms 降至 0.5ms)</p>
|
||
</div>
|
||
|
||
<div class="controls">
|
||
<button class="btn-primary" onclick="runTest(50)">测试 50 个公式</button>
|
||
<button class="btn-primary" onclick="runTest(100)">测试 100 个公式</button>
|
||
<button class="btn-secondary" onclick="viewCacheStats()">查看缓存统计</button>
|
||
<button class="btn-secondary" onclick="clearCache()">清空缓存</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="comparison" id="results-area"></div>
|
||
|
||
<div class="formulas-preview">
|
||
<h2>📝 测试公式预览</h2>
|
||
<div id="formulas-preview"></div>
|
||
</div>
|
||
|
||
<div class="test-card">
|
||
<h2>📝 测试日志</h2>
|
||
<div id="console-log" class="console-log">等待测试...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 加载依赖 -->
|
||
<script src="https://gcore.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
||
<script src="../../js/chatbot/utils/katex-cache.js"></script>
|
||
|
||
<script>
|
||
// 测试公式集合
|
||
const TEST_FORMULAS = [
|
||
'E = mc^2',
|
||
'\\int_{0}^{\\infty} e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}',
|
||
'\\sum_{n=1}^{\\infty} \\frac{1}{n^2} = \\frac{\\pi^2}{6}',
|
||
'x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}',
|
||
'f(x) = \\frac{1}{\\sigma\\sqrt{2\\pi}} e^{-\\frac{(x-\\mu)^2}{2\\sigma^2}}',
|
||
'\\nabla \\times \\vec{E} = -\\frac{\\partial \\vec{B}}{\\partial t}',
|
||
'\\lim_{n \\to \\infty} \\left(1 + \\frac{1}{n}\\right)^n = e',
|
||
'\\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix} \\begin{pmatrix} x \\\\ y \\end{pmatrix} = \\begin{pmatrix} ax+by \\\\ cx+dy \\end{pmatrix}',
|
||
'P(A|B) = \\frac{P(B|A)P(A)}{P(B)}',
|
||
'\\oint_{\\partial S} \\vec{E} \\cdot d\\vec{l} = -\\frac{d}{dt}\\int_S \\vec{B} \\cdot d\\vec{A}'
|
||
];
|
||
|
||
function log(message, type = 'info') {
|
||
const logEl = document.getElementById('console-log');
|
||
const timestamp = new Date().toLocaleTimeString();
|
||
logEl.innerHTML += `<span class="${type}">[${timestamp}] ${message}</span>\n`;
|
||
logEl.scrollTop = logEl.scrollHeight;
|
||
console.log(`[${type.toUpperCase()}] ${message}`);
|
||
}
|
||
|
||
// 测试无缓存渲染
|
||
async function testWithoutCache(formulas, rounds = 3) {
|
||
log('开始测试:无缓存渲染', 'info');
|
||
|
||
const times = [];
|
||
|
||
for (let round = 0; round < rounds; round++) {
|
||
const roundStart = performance.now();
|
||
|
||
formulas.forEach(tex => {
|
||
katex.renderToString(tex, {
|
||
displayMode: true,
|
||
output: 'html',
|
||
strict: 'ignore',
|
||
throwOnError: false
|
||
});
|
||
});
|
||
|
||
times.push(performance.now() - roundStart);
|
||
}
|
||
|
||
const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
|
||
log(`无缓存完成: 平均 ${avgTime.toFixed(0)} ms`, 'success');
|
||
|
||
return {
|
||
avgTime,
|
||
minTime: Math.min(...times),
|
||
maxTime: Math.max(...times),
|
||
totalRenders: formulas.length * rounds
|
||
};
|
||
}
|
||
|
||
// 测试缓存渲染
|
||
async function testWithCache(formulas, rounds = 3) {
|
||
log('开始测试:缓存渲染', 'info');
|
||
|
||
// 第一轮:缓存未命中
|
||
const firstRoundStart = performance.now();
|
||
formulas.forEach(tex => {
|
||
window.renderKatexCached(tex, {
|
||
displayMode: true,
|
||
output: 'html',
|
||
strict: 'ignore',
|
||
throwOnError: false
|
||
});
|
||
});
|
||
const firstRoundTime = performance.now() - firstRoundStart;
|
||
log(`第一轮(缓存未命中): ${firstRoundTime.toFixed(0)} ms`, 'info');
|
||
|
||
// 后续轮次:缓存命中
|
||
const times = [];
|
||
for (let round = 0; round < rounds - 1; round++) {
|
||
const roundStart = performance.now();
|
||
|
||
formulas.forEach(tex => {
|
||
window.renderKatexCached(tex, {
|
||
displayMode: true,
|
||
output: 'html',
|
||
strict: 'ignore',
|
||
throwOnError: false
|
||
});
|
||
});
|
||
|
||
times.push(performance.now() - roundStart);
|
||
}
|
||
|
||
const avgCachedTime = times.reduce((a, b) => a + b, 0) / times.length;
|
||
log(`后续轮次(缓存命中): 平均 ${avgCachedTime.toFixed(2)} ms`, 'success');
|
||
|
||
return {
|
||
firstRoundTime,
|
||
avgCachedTime,
|
||
minTime: Math.min(...times),
|
||
maxTime: Math.max(...times),
|
||
totalRenders: formulas.length * rounds
|
||
};
|
||
}
|
||
|
||
// 运行测试
|
||
async function runTest(formulaCount) {
|
||
log('========================================', 'info');
|
||
log(`开始测试:${formulaCount} 个公式,每个渲染 3 次`, 'info');
|
||
log('========================================', 'info');
|
||
|
||
// 准备测试公式(重复使用基础公式)
|
||
const formulas = [];
|
||
for (let i = 0; i < formulaCount; i++) {
|
||
formulas.push(TEST_FORMULAS[i % TEST_FORMULAS.length]);
|
||
}
|
||
|
||
// 清空缓存
|
||
window.katexCache.clear();
|
||
|
||
// 测试无缓存
|
||
const noCacheResult = await testWithoutCache(formulas, 3);
|
||
|
||
// 等待一下
|
||
await new Promise(r => setTimeout(r, 500));
|
||
|
||
// 测试缓存
|
||
const cacheResult = await testWithCache(formulas, 3);
|
||
|
||
// 显示结果
|
||
displayResults(noCacheResult, cacheResult, formulaCount);
|
||
|
||
// 显示公式预览
|
||
displayFormulas(formulas.slice(0, 10));
|
||
|
||
log('========================================', 'success');
|
||
log('测试完成!', 'success');
|
||
log('========================================', 'success');
|
||
}
|
||
|
||
// 显示结果
|
||
function displayResults(noCacheResult, cacheResult, formulaCount) {
|
||
const improvement = ((noCacheResult.avgTime - cacheResult.avgCachedTime) / noCacheResult.avgTime * 100);
|
||
|
||
const html = `
|
||
<div class="test-card">
|
||
<h3>⏱️ 无缓存渲染</h3>
|
||
<div class="metric warning">
|
||
<span>平均耗时:</span>
|
||
<span><strong>${noCacheResult.avgTime.toFixed(0)} ms</strong></span>
|
||
</div>
|
||
<div class="metric">
|
||
<span>最小耗时:</span>
|
||
<span>${noCacheResult.minTime.toFixed(0)} ms</span>
|
||
</div>
|
||
<div class="metric">
|
||
<span>最大耗时:</span>
|
||
<span>${noCacheResult.maxTime.toFixed(0)} ms</span>
|
||
</div>
|
||
<div class="metric">
|
||
<span>总渲染数:</span>
|
||
<span>${noCacheResult.totalRenders} 次</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="test-card">
|
||
<h3>⚡ 缓存渲染</h3>
|
||
<div class="metric highlight">
|
||
<span>缓存命中耗时:</span>
|
||
<span><strong>${cacheResult.avgCachedTime.toFixed(2)} ms</strong></span>
|
||
</div>
|
||
<div class="metric">
|
||
<span>首次未命中:</span>
|
||
<span>${cacheResult.firstRoundTime.toFixed(0)} ms</span>
|
||
</div>
|
||
<div class="metric highlight">
|
||
<span>性能提升:</span>
|
||
<span><strong>↑ ${improvement.toFixed(1)}%</strong></span>
|
||
</div>
|
||
<div class="metric highlight">
|
||
<span>速度倍数:</span>
|
||
<span><strong>${(noCacheResult.avgTime / cacheResult.avgCachedTime).toFixed(0)}x 更快</strong></span>
|
||
</div>
|
||
|
||
<div class="huge-improvement">
|
||
🚀 ${improvement.toFixed(0)}% 性能提升!
|
||
</div>
|
||
|
||
<div class="success-box">
|
||
<h3>✅ 实际应用场景</h3>
|
||
<p>如果你的 chatbot 有 ${formulaCount} 个公式:</p>
|
||
<ul style="margin: 8px 0 0 20px; line-height: 1.8;">
|
||
<li><strong>无缓存:</strong> 每次打开需要 ${noCacheResult.avgTime.toFixed(0)} ms</li>
|
||
<li><strong>有缓存:</strong> 第二次打开只需 <strong>${cacheResult.avgCachedTime.toFixed(2)} ms</strong></li>
|
||
<li><strong>节省时间:</strong> <strong>${(noCacheResult.avgTime - cacheResult.avgCachedTime).toFixed(0)} ms</strong></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.getElementById('results-area').innerHTML = html;
|
||
}
|
||
|
||
// 显示公式预览
|
||
function displayFormulas(formulas) {
|
||
const preview = document.getElementById('formulas-preview');
|
||
preview.innerHTML = '<p style="margin-bottom: 12px; color: #666;">测试公式示例(仅显示前 10 个):</p>';
|
||
|
||
formulas.forEach((tex, index) => {
|
||
const div = document.createElement('div');
|
||
div.style.margin = '12px 0';
|
||
div.style.padding = '12px';
|
||
div.style.background = '#f8f9fa';
|
||
div.style.borderRadius = '4px';
|
||
|
||
try {
|
||
const html = window.renderKatexCached(tex, {
|
||
displayMode: true,
|
||
output: 'html',
|
||
strict: 'ignore',
|
||
throwOnError: false
|
||
});
|
||
div.innerHTML = `<div style="color: #666; font-size: 12px; margin-bottom: 4px;">公式 ${index + 1}</div>${html}`;
|
||
} catch (e) {
|
||
div.textContent = `公式 ${index + 1}: 渲染失败`;
|
||
}
|
||
|
||
preview.appendChild(div);
|
||
});
|
||
}
|
||
|
||
// 查看缓存统计
|
||
function viewCacheStats() {
|
||
window.getKatexCacheStats();
|
||
}
|
||
|
||
// 清空缓存
|
||
function clearCache() {
|
||
window.katexCache.clear();
|
||
log('缓存已清空', 'warning');
|
||
alert('缓存已清空!');
|
||
}
|
||
|
||
// 页面加载完成
|
||
window.addEventListener('load', function() {
|
||
log('页面加载完成', 'success');
|
||
log('KaTeX 缓存系统已就绪', 'info');
|
||
log('点击上方按钮开始测试', 'info');
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|