<!-- title: 不读每个字：ripgrep凭什么快十倍 -->
<!-- domain: programming -->
<!-- video: https://www.youtube.com/watch?v=F8JU70z2mkw -->

# 不读每个字：ripgrep凭什么快grep十倍

## 导语

如果你写过几年代码，大概都经历过这样一个瞬间：在几十万行的代码库里 `grep` 一个变量名，终端像卡住了一样转了十几秒才吐出结果。然后有人告诉你："试试 `rg`（ripgrep）。"你半信半疑地敲下去，结果几乎是回车即出。同一个搜索，ripgrep 经常比 GNU grep 快上数倍，比 The Silver Searcher、ack 这类"现代替代品"快得更多。

这个视频做的，就是把这种"快得离谱"的黑盒撬开给你看。但老实说，单靠视频那十几分钟的演示，你只会记住"ripgrep 很快"这个结论，却抓不住它**为什么快**。真正值钱的洞察，藏在作者 Andrew Gallant（网名 BurntSushi）那篇 2016 年的经典长文里——ripgrep 的速度不是任何一招鲜，而是把**字符串搜索、自动机理论、CPU 内存层级、并行调度**四件事拧成一股绳的工程组合拳。

这篇解读的任务，就是把那股绳拆开，再帮你重新编好。看完你会明白：ripgrep 真正颠覆的，是"搜索"这件事在计算机里到底该怎么算——它本质上不是在"读文本"，而是在"让字节自己撞进一张预先布好的网"。掌握这个思维，你以后看任何"为什么 X 这么快"的问题，都会多一个趁手的分析框架。

## 视频背景卡片

| 项目 | 内容 |
|------|------|
| 视频标题 | I Thought GREP Was Enough… Then Ripgrep Made Me Faster |
| 核心主题 | ripgrep 相对 grep 的速度优势与日常实战用法 |
| 作者/背景 | 基于 BurntSushi（Andrew Gallant）用 Rust 开发的 ripgrep |
| 首发标杆文 | 《ripgrep is faster than {grep, ag, git grep, ucg, pt, sift}》(2016) |
| 关键技术栈 | Rust `regex` crate · 惰性 DFA · Teddy SIMD · mmap · 多线程 |
| 核心论点 | 速度来自"字面量预过滤 + 自动机引擎 + 智能剪枝"的组合，而非单一技巧 |

## 核心内容拆解

### 一、先认清敌人：回溯型正则的"指数级陷阱"

要理解 ripgrep 为什么快，得先看它的对手——传统正则引擎——慢在哪。视频里一笔带过的"正则可能很慢"，背后的真相比想象中严重得多。

主流的 PCRE、Python `re`、Java、Ruby 的正则引擎，普遍采用**回溯（backtracking）**算法来支持回溯引用、前瞻后顾这些"高级特性"。回溯引擎在遇到歧义模式时，会一条路一条路地试，走不通就退回来换一条。问题在于，这种试探在某些输入上会**指数级爆炸**——也就是著名的 ReDoS（正则拒绝服务）漏洞。一段几十个字符的恶意字符串，就能让一个回溯引擎卡死几小时甚至几年。

ripgrep 的底层引擎（Rust 的 `regex` crate）做了一个近乎"反潮流"的决定：**放弃回溯，只用有限自动机（finite automata）**。它只支持正则的"正交子集"——那些能被 NFA/DFA 精确描述的模式，拒绝任何会导致回溯的特性。代价是你写不了某些花哨的正则；回报是，**它对任何输入都能保证线性时间**，永远不会出现"卡死"。这是 ripgrep 一切速度的地基：一个不会跑歪的引擎，才值得在上面叠加优化。

> 这是 BurntSushi 博客里点出的关键设计哲学，视频没有展开——但对理解整个工具至关重要。

### 二、第一招：先找"字面量"，再跑正则

很多人以为 ripgrep 拿到 `foo.*bar` 这样的正则后，就老老实实对每个字符跑状态机。错了。它的第一步是**把正则里的字面子串抽出来**。

引擎会分析模式，发现里面有一个固定字面量 `foo`。于是它根本不急着跑那个 `.*` 的正则，而是先用一个**极快的字面量搜索**去文本里找 `foo` 出现的位置。只有当 `foo` 这个"嫌疑位置"被找到后，它才在附近启动完整的正则状态机去验证 `.*bar` 是否真的成立。如果整段文本里压根没有 `foo`，正则引擎几乎一行都不用真正"跑"。

这就是 ripgrep 最关键的一个思想：**绝大多数搜索请求，瓶颈不在正则本身，而在于你用多快的速度跳过那些根本不可能匹配的区域**。博客里反复出现 128 次的 "literal" 字样，正说明作者把多少心血压在了这一层。这个"字面量预过滤（literal prefilter）"的思路，也是后来无数高性能搜索工具的共同套路。

### 三、第二招：SIMD 一次吃 16/32 个字节，把"找字面量"也榨干

光有"先找字面量"还不够，因为"找字面量"本身也是一个搜索问题。如果还是逐字节比对，速度依然上不去。ripgrep 在这一层祭出了真正的硬件杀手锏——**SIMD（单指令多数据）**。

现代 CPU 都有 128 位（SSE）或 256 位（AVX2）的向量寄存器，一条指令能同时处理 16 或 32 个字节。ripgrep 用的是一套叫 **Teddy** 的多模式 SIMD 搜索算法。Teddy 不是 BurntSushi 发明的，它出自 Intel 的 Hyperscan 正则库，由工程师 Geoffrey Langdale 设计。BurntSushi 把它移植到了 Rust 的 `regex` crate 里。

Teddy 的精妙之处在于：它能**同时搜索多个字面量**，并且利用 SIMD 把每个 16/32 字节的窗口一次性筛一遍，用位运算快速判断"这个窗口里有没有可能出现我要找的字节组合"。只有窗口亮了红灯，才回退到精确验证。这意味着 ripgrep 不是"一个字符一个字符地读文本"，而是"一把一把地吞字节"，吞吐量直接乘以 16。

> 视频把这个细节简化成了"ripgrep 用了 SIMD"，但 Teddy 多模式并行扫描这个内核，才是 SIMD 真正发挥威力的地方——尤其当正则里能抽出多个字面量时（比如 `(cat|dog|bird)`），收益会被进一步放大。

### 四、第三招：惰性 DFA——把正则编译成"会自己走的状态机"

字面量搜到了候选位置，接下来才是真正"跑正则"。这里 ripgrep 用的是**确定性有限自动机（DFA）**。

回溯引擎是"试探+退回"，而 DFA 是一条**确定性**的路径：当前处于哪个状态、读入哪个字节，下一个状态完全确定，没有任何分叉、没有回退。每个字节处理时间恒定，整体就是严格的 O(n) 线性时间。更妙的是，ripgrep 的引擎**把 UTF-8 解码直接编译进了状态机**——也就是说，它不需要先把字节流解码成 Unicode 字符再匹配，状态机自己就知道怎么在原始字节流上一边"读懂 UTF-8"一边匹配 `\p{Greek}` 这样的 Unicode 类。

但 DFA 有个老问题：某些正则会产生**状态爆炸**，把 DFA 变成天文数字的状态表。ripgrep（继承 Rust regex 的设计）用的是**惰性 DFA（lazy DFA）**：它不预先构建整张状态表，而是**边跑边建**——只有真正被访问到的状态才会被生成并缓存。这样既保留了 DFA 的线性速度，又规避了状态爆炸。博客里 "dfa" 出现 25 次，足见这一层的分量。

### 五、第四招：内存映射（mmap）——让内核替你把字节搬进内存

读完算法，还有一道"搬运"的坎：文件在硬盘上，程序怎么把它读进来？最朴素的方式是 `read()` 系统调用，把字节一段段拷进用户态缓冲区。ripgrep 的另一招是 **mmap（内存映射）**——把文件直接映射到进程的虚拟地址空间，让操作系统的页缓存（page cache）在背后按需把磁盘页调入内存。

好处是：避免了用户态和内核态之间的字节拷贝；多个进程读同一个文件时能共享同一份页缓存；访问模式交给 OS 调度，往往更聪明。对于单个大文件的搜索，mmap 通常更快。

但这里有个**反直觉的细节**，恰恰体现了 BurntSushi 作为工程师的清醒：他明确指出，**"内存映射总是更快"是个误区**。当你要搜索成千上万个**小文件**时，mmap 反而可能更慢——因为每个文件都要建立映射、产生缺页中断，开销被摊薄不掉。所以 ripgrep 并不无脑用 mmap，而是用启发式策略：大文件用 mmap，小文件用普通读缓冲。这种"知道何时**不**用某项优化"的判断力，比优化本身更难得。

### 六、第五招：智能剪枝 + 多线程——不搜不该搜的东西

前面几招都是"搜得更快"，这一招是"**根本不搜**"。博客里点明：ripgrep 不仅引擎快，它还"tries to be smart about what it searches"。

具体来说，ripgrep 默认会**尊重 `.gitignore`**——那些被版本控制忽略的 `node_modules`、`target`、构建产物，它直接跳过，连打开都不打开。它还会**自动跳过二进制文件**和隐藏文件。对于真实代码库，这一步经常比任何算法优化都更立竿见影：你以为在搜 50 万行代码，剪枝后真正要扫的可能只有 5 万行。

剪枝之后，剩下的文件被**分发给多个线程并行搜索**。ripgrep 用工作窃取式的调度，把文件分摊到 CPU 各个核心上同时跑。这里有个工程难点：多线程共享正则引擎的 DFA 缓存会引发竞争，所以 ripgrep 在线程同步上做了精心设计（mutex 保护的只读状态 + 每线程本地状态），既榨干并行度又不被锁拖慢。

### 七、为什么 GNU grep 也很快，ripgrep 还是赢了？

公平地说，**GNU grep 本身并不慢**。它在底层也用了 Aho-Corasick、Boyer-Moore 这些经典的高效字面量算法，单文件搜索速度极强。视频里"快十倍"更多是相对 The Silver Searcher、ack 这些工具，以及"在真实代码库里的端到端体验"。

那 ripgrep 相对 GNU grep 的优势到底在哪？主要有三点：第一，**默认尊重 `.gitignore`**——grep 默认不尊重，会把 `node_modules` 一类垃圾全扫一遍；第二，**多线程并行**——grep 是单线程；第三，**Rust regex 的"字面量+惰性 DFA"组合**在复杂 Unicode 正则上比 grep 的回退引擎更稳，且不会 ReDoS。再加上 Rust 带来的内存安全和跨平台一致体验，ripgrep 才在开发者工具的生态里站稳了"现代默认"的位置。

> 这一层对比是我基于博客的延伸分析，帮你厘清"快"到底快在引擎、还是快在工程取舍。

## 知识结构图

```text
flowchart TD
    A[用户输入正则模式<br/>如 foo.*bar] --> B{引擎分析阶段}
    B --> C[抽取出字面量 foo]
    B --> D[惰性构建 DFA 状态机]
    C --> E[Teddy SIMD 多模式扫描<br/>一次吃 16/32 字节]
    E --> F{窗口命中字面量?}
    F -->|否| G[整块跳过,不跑正则]
    F -->|是| H[在候选位置启动 DFA 验证]
    D --> H
    H --> I[O(n) 线性输出匹配]
    J[文件读取层] --> K{文件大小判断}
    K -->|大文件| L[mmap 内存映射<br/>内核页缓存搬运]
    K -->|海量小文件| M[普通 read 缓冲]
    L --> E
    M --> E
    N[调度与剪枝层] --> O[尊重 .gitignore<br/>跳过二进制/隐藏文件]
    N --> P[多线程并行分发文件]
    P --> E
    style E fill:#ffe4b5
    style H fill:#b5e4ff
    style G fill:#d4f4dd
```

## 实战启示

**对日常开发者：** 这套拆解最大的迁移价值，不是让你去重写一个 ripgrep，而是给你一个**判断"性能问题出在哪一层"的思维模型**。下次你的搜索/过滤逻辑慢了，别急着换语言、换库，先按 ripgrep 的五层过一遍：

1. 你的算法是不是**回溯型**的？会不会在某些输入上指数爆炸？（→ 换成自动机/状态机思路）
2. 你能不能**先做廉价的字面量/哈希预过滤**，把昂贵的正则/解析推迟到真正的候选上？
3. 你的热点循环能不能上 **SIMD / 批处理**，从"逐个处理"改成"成把处理"？
4. 你的**数据搬运**（IO、序列化、拷贝）是不是瓶颈？mmap、零拷贝、流式处理能不能帮忙？而且——你知道什么时候**不该**用它们吗？
5. 你是不是在**做大量无用功**？能不能用规则提前剪枝（忽略、短路、跳过）？

这个清单比任何具体技巧都值钱，因为它适用于字符串搜索之外的几乎所有性能战场——从日志扫描、数据清洗到推荐召回。

**对工程审美：** ripgrep 还示范了一种被低估的品质——**"知道何时不优化"**。它放弃回溯换来可预测性，在小文件上放弃 mmap 换来吞吐，在正则特性上做减法换来线性保证。真正的性能高手，往往不是把所有优化全开，而是对每一层都清楚它的**代价边界**。这一点，比"它用了 SIMD"更值得记一辈子。

**一个可立刻上手的小练习：** 在你最大的代码库里分别跑 `time grep -rn "PATTERN" .` 和 `time rg "PATTERN"`，再各加一次忽略 `.gitignore` 的对照。亲眼看到数量级的差异，这套理论就会从"别人的故事"变成"你的肌肉记忆"。

## 延伸学习路径

1. **《ripgrep is faster than {grep, ag, git grep, ucg, pt, sift}》** — BurntSushi 官方博客，本文的核心技术依据，逐个 benchmark 拆解，是理解 ripgrep 必读的一手资料。
2. **Rust `regex` crate 文档与博客** — 作者关于"惰性 DFA""字面量提取"的系列文章，自动机工程化的最佳教材。
3. **Russ Cox《Implementing Regular Expressions》系列**（swtch.com） — 用 Thompson NFA/DFA 讲透"为什么正则不该回溯"，是理解 ripgrep 引擎哲学的源头。
4. **Intel Hyperscan 与 Teddy 算法资料** — Geoffrey Langdale 关于 SIMD 多模式字符串搜索的原始论述，想深挖 SIMD 那一层必看。
5. **《Crafting Interpreters》/《Writing An Interpreter In Go》** — 若你想亲手实现一个状态机/DFA，这两本能把自动机从抽象理论变成可跑的代码。

## 一句话总结

ripgrep 的快，不是因为它读了更多文本，而是因为它**用字面量预过滤 + SIMD 批扫描，让绝大多数字节连正则都不用跑**——性能的终极秘密，永远是"少干活"。

## 参考来源

1. [I Thought GREP Was Enough… Then Ripgrep Made Me Faster](https://www.youtube.com/watch?v=F8JU70z2mkw) — 本文分析的核心视频
2. [ripgrep is faster than {grep, ag, git grep, ucg, pt, sift} — Andrew Gallant's Blog](https://blog.burntsushi.net/ripgrep/) — 作者权威长文，技术细节主要来源
3. [What makes ripgrep so fast? — r/rust](https://www.reddit.com/r/rust/comments/sr02aj/what_makes_ripgrep_so_fast/) — 社区深度讨论与补充视角
4. [Regex Engines — BurntSushi/ripgrep DeepWiki](https://deepwiki.com/BurntSushi/ripgrep/2.4-regex-engines) — 引擎架构系统化梳理
5. [ripgrep Performance — 用户指南](https://ripgrep.dev/benchmarks/) — benchmark 与性能特征对照
> 本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵**爱卫生**（微信ID: gprshome201101）维护，聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。
> 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。
### 完成摘要

**关键洞察提炼：** ripgrep 的速度本质是"少干活"——字面量预过滤 + SIMD 批扫描让绝大多数字节不触发正则，惰性 DFA 保证线性时间，mmap/剪枝/并行各司其职，且工程师"知道何时不用某优化"是更难得的品质。

**注意事项：**
- 锚定视频为真实存在的 ripgrep 讲解视频（长视频资源较稀缺，该主题深度内容主要沉淀在 BurntSushi 博客，已在文中透明标注视频观点与博客延伸的边界）
- 临时缓存文件 `/home/gprshome/rg_blog.txt` 已生成，如不需要可删除

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
