首页 › programming › 不读每个字:ripgrep凭什么快十倍

不读每个字: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 才在开发者工具的生态里站稳了"现代默认"的位置。

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

知识结构图

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 — 本文分析的核心视频
  2. ripgrep is faster than {grep, ag, git grep, ucg, pt, sift} — Andrew Gallant's Blog — 作者权威长文,技术细节主要来源
  3. What makes ripgrep so fast? — r/rust — 社区深度讨论与补充视角
  4. Regex Engines — BurntSushi/ripgrep DeepWiki — 引擎架构系统化梳理
  5. ripgrep Performance — 用户指南 — 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学通信知识星球海报-扫码加入