消除冗余的 Markdown 解析:通常提升 2‑10 倍的 AI Streaming
Source: Dev.to
(未提供需要翻译的正文内容,无法进行翻译。)
问题
当 AI 流式输出新的文本块时,传统的 markdown 解析器会 从头重新解析整个文档,在已经渲染的内容上浪费 CPU 周期。Incremark 通过仅解析文档的 新部分 来解决此问题。
Source: …
基准测试结果 – 眼见为实
短 Markdown 文档
较长的 Markdown 文档
注意: 由于分块策略的不同,性能提升倍率在不同运行之间可能会有所变化。
演示页面使用随机分块长度:
const chunks = content.match(/[\s\S]{1,20}/g) || []
这模拟了真实场景——一个块可能包含前一个或后一个块的内容。无论内容如何分块,性能提升都是有保障的。演示 不 使用人为分块来夸大结果。
实时演示
- Vue 演示:
- React 演示:
- 文档:
对于极长的 Markdown 文档,提升更为显著。一个 20 KB 的 Markdown 基准测试实现了 46 倍 的速度提升。内容越长,提速越大——理论上没有上限。
关键优势
| ✅ | 优势 |
|---|---|
| ⚡ | 在 AI 流式场景下通常 2–10× 更快 |
| 🚀 | 对于更长的文档 提升更显著(已测试最高 46×) |
| 🎯 | 零冗余解析 – 每个字符仅解析一次 |
| ✨ | 为 AI 流式优化的 增量更新 |
| 💪 | 同样支持 普通 markdown |
| 🔧 | 框架支持 – 可直接使用的 React 和 Vue 组件 |
为什么这么快?
传统解析器的问题
在构建 AI 聊天应用时,AI 会以小块的形式流式输出内容。每当一个块到达后,整个 markdown 字符串都会被送入解析器(例如 remark、marked.js、markdown‑it)。这些解析器 每次都会重新解析整个文档,即使是已经渲染好的稳定部分也会被重新解析。这会导致巨大的性能浪费。
像 vue‑stream‑markdown 之类的工具通过复用稳定组件提升了 渲染 层,但它们 并没有消除 markdown 文本的重复解析——这才是 CPU 的真正瓶颈。
Incremark 的核心性能优化
Incremark 的突破在于 解析阶段:它 只解析不稳定的 markdown 块,永不重新解析已经稳定的块。这将解析复杂度从 O(n²) 降低到 O(n),也就是说输出越长,性能提升越明显。
1. 增量解析 – 从 O(n²) 到 O(n)
传统解析器在每次更新时都会重新解析整个文档,导致二次方的工作量增长。Incremark 的 IncremarkParser 类实现了增量策略(见 IncremarkParser.ts):
// Design Philosophy:
// 1. Maintain a text buffer to receive streaming input
// 2. Identify "stable boundaries" and mark completed blocks as 'completed'
// 3. For blocks currently being received, re-parse only that block's content
// 4. Complex nested nodes are treated as a whole until confirmed complete
2. 智能边界检测
append() 方法内部的 findStableBoundary() 是关键优化点:
append(chunk: string): IncrementalUpdate {
this.buffer += chunk
this.updateLines()
const { line: stableBoundary, contextAtLine } = this.findStableBoundary()
if (stableBoundary >= this.pendingStartLine && stableBoundary >= 0) {
// Only parse newly completed blocks, never re-parse already completed content
const stableText = this.lines
.slice(this.pendingStartLine, stableBoundary + 1)
.join('\n')
const ast = this.parse(stableText)
// …
}
}
3. 状态管理以避免冗余计算
解析器维护了多项状态,用来防止重复工作:
| 状态 | 目的 |
|---|---|
buffer | 累积的未解析内容 |
completedBlocks | 永不重新解析的块集合 |
lineOffsets | 行长度的前缀和,实现 O(1) 的行‑到‑偏移查找 |
pendingStartLine | 当前接收块的第一行索引 |
stableBoundary | 已确定为稳定的最后一行 |
通过仅更新新出现的稳定区域,Incremark 实现了 线性时间的增量解析。
入门
# Install the core library
npm i incremark
# For Vue
npm i incremark-vue
# For React
npm i incremark-react
// React example
import { IncremarkRenderer } from 'incremark-react'
function ChatMessage({ stream }) {
return <IncremarkRenderer markdown={stream} />
}
<script setup>
import { IncremarkRenderer } from 'incremark-vue'
defineProps({ stream: String })
</script>
<template>
<IncremarkRenderer :markdown="stream" />
</template>
许可证
Incremark 在 MIT 许可证 下发布。欢迎使用、修改和贡献!
增量计算
context:跟踪代码块、列表等的嵌套状态。
4. 增量行更新优化
updateLines() 方法仅处理新内容,避免完整的拆分操作:
private updateLines(): void {
// Find the last incomplete line (which may be continued by a new chunk)
const lastLineStart = this.lineOffsets[prevLineCount - 1];
const textFromLastLine = this.buffer.slice(lastLineStart);
// Re‑split only the last line and subsequent content
const newLines = textFromLastLine.split('\n');
// Only update the changed portions
}
性能比较
| 文档大小 | 传统解析器(字符) | Incremark(字符) | 减少率 |
|---|---|---|---|
| 1 KB | 1,010,000 | 20,000 | 98 % |
| 5 KB | 25,050,000 | 100,000 | 99.6 % |
| 20 KB | 400,200,000 | 400,000 | 99.9 % |
关键不变量
Incremark 的性能优势源于一个关键不变量:一旦块被标记为完成,就永不重新解析。这确保每个字符最多只被解析一次,实现 O(n) 的时间复杂度。
🚀 立即开始
停止在冗余解析上浪费 CPU 周期。今天就试试 Incremark:
快速安装
npm install @incremark/core
# For React
npm install @incremark/react
# For Vue
npm install @incremark/vue
资源
- 📚 文档
- 🎮 在线演示 (Vue)
- 🎮 在线演示 (React)
- 💻 GitHub 仓库
用例
- 🤖 带流式响应的 AI 聊天应用
- ✍️ 实时 Markdown 编辑器
- 📝 实时协作文档
- 📊 带 Markdown 内容的流式数据仪表盘
- 🎓 互动学习平台
无论是构建 AI 界面,还是仅仅想要更快的 Markdown 渲染,Incremark 都能提供您所需的性能。
💬 试用并展示您的支持
我非常希望您 在项目中尝试 Incremark,亲自感受性能差异!实时演示是体验速度提升的最佳方式。
如果您觉得 Incremark 有用,请考虑 在 GitHub 上为它点个 ⭐ 星——这真的能帮助项目获得更多关注,也能激励我继续改进。欢迎您提供反馈、提交问题以及贡献代码!
- 🌟 在 GitHub 上加星
- 💡 报告问题或提出想法
- 🤝 贡献代码:欢迎提交 Pull Request!
感谢您对 Incremark 的关注!让我们一起让 Markdown 渲染更快 🚀

