
ripgrep:比 grep 快 10 倍的命令行搜索工具
ripgrep (rg) 是一个用 Rust 编写的命令行搜索工具,比传统 grep 快 10 倍。本文深入分析其架构设计、正则引擎优化和 25 项基准测试,揭示它为何能同时实现极致性能和正确性。
原文来源:Andrew Gallant 的博客 — ripgrep 的作者对这款工具的深度技术剖析,包含 25 项详细的基准测试和性能分析。
概述
ripgrep(简称 rg)是一个用 Rust 编写的命令行搜索工具,集速度、跨平台(支持 Linux、macOS 和 Windows)和正确性于一身。
通过 25 项基准测试,我们可以支撑以下三大主张:
- 无论是搜索单个文件还是巨大的目录树,在性能或正确性上,没有其他搜索工具能打败 ripgrep。
- ripgrep 是唯一一个提供完整 Unicode 支持且不会为此牺牲性能的工具。
- 搜索多文件的工具通常被内存映射拖慢,而 ripgrep 不会被这个问题影响。
本文由 ripgrep 的作者 Andrew Gallant 撰写。他同时是 Rust 正则表达式库的核心作者,在 Rust 文本搜索领域已有超过 2.5 年的深耕经验。本文将从底层深入分析每个搜索工具的性能表现。
为什么选择 ripgrep?
ripgrep 可以取代许多其他搜索工具的使用场景,因为它包含了大多数功能且通常更快。
核心特性
- 递归搜索默认开启,自动忽略
.gitignore中的文件、隐藏文件和二进制文件 - 完整的
.gitignore支持,而其他声称支持此功能的工具存在许多 bug - 按文件类型搜索,例如
rg -t py foo只搜索 Python 文件,rg -T js foo排除 JavaScript 文件 - 支持 PCRE2 正则引擎(需
--pcre2开启),可使用前瞻/后顾断言和反向引用 - 支持多种文本编码:UTF-16、latin-1、GBK、EUC-JP、Shift_JIS 等
- 支持搜索压缩文件:gzip、xz、bzip2 等
- 支持任意输入预处理过滤器:PDF 文本提取、解密、自动编码检测等
为什么不使用 ripgrep?
- 你需要一个符合 POSIX 标准的便携通用工具——选择经典 grep
- 你依赖其他工具的某些特定功能或 bug
- 你的平台不支持 ripgrep
- 存在 ripgrep 表现不佳的边缘场景
安装
macOS(Homebrew):
brew install ripgrep从源码编译:
git clone git://github.com/BurntSushi/ripgrep
cd ripgrep
cargo build --release
./target/release/rg --version启用 SIMD 加速(需 Rust nightly):
cargo build --release --features simd-accel快速上手
# 递归搜索当前目录,遵守 .gitignore,跳过隐藏和二进制文件
rg "pattern"
# 搜索所有文件(无视忽略规则)
rg -u "pattern"
# 大小写不敏感搜索
rg -i "pattern"
# 显示匹配前后各 2 行
rg -C 2 "pattern"
# 精确单词匹配
rg -w "pattern"
# 搜索和替换
rg '([A-Z][a-z]+)\s+([A-Z][a-z]+)' --replace '$2, $1'
# 按文件类型筛选
rg -t py "class Foo"
rg -t html -t css "stylesheet"
# 排除特定文件
rg --glob '!*.min.js' "pattern"搜索工具的工作原理
在深入基准测试之前,了解 grep 类搜索工具的整体架构至关重要。
1. 文件发现
第一步是确定要搜索哪些文件。
- 传统 grep:直接搜索命令行指定的文件,或递归扫描所有文件
- The Silver Searcher 模式:智能默认行为——递归搜索但跳过
.gitignore、隐藏文件和二进制文件 - ripgrep:结合两者优点——智能默认搜索 + 快速正则引擎 + 并行化
ripgrep 在文件发现阶段需要高效完成三件事:
- 快速的递归目录遍历,最小化不必要的
stat调用 - 快速的文件路径过滤,正确应用
.gitignore规则 - 将文件分配给工作线程进行并行搜索
2. 正则引擎
正则引擎分为两大类:
| 类型 | 代表 | 特点 |
|---|---|---|
| 回溯型 | PCRE、Python re | 功能丰富,支持前瞻/后顾/反向引用,但可能指数级慢 |
| 有限自动机 | Rust regex、RE2、Go regex | 线性时间复杂度保证,但功能较少 |
各工具使用的正则引擎:
| 工具 | 正则引擎 |
|---|---|
| ripgrep | Rust regex 库(有限自动机) |
| The Silver Searcher | PCRE(回溯) |
| Universal Code Grep | PCRE(回溯) |
| The Platinum Searcher | Go regex(有限自动机) |
| sift | Go regex(有限自动机) |
| GNU grep | 自研 DFA + 回退策略 |
回溯型引擎的一个典型问题:
import re
re.search('(a|aa|aaa)+b', 'a' * 100) # 可能需要很长时间3. 字面量优化
Boyer-Moore 算法:经典子串搜索算法,通过预计算的跳跃表跳过某些字符。在现代 CPU 上,关键在于多快能识别候选匹配——通常借助 SIMD 指令实现每秒数 GB 的吞吐量。
字面量提取:Rust 的正则库会从模式中提取字面量前缀/后缀。例如:
foo\w+bar→ 提取foo和barfoo|bar→ 提取foo和bar作为交替
内联字面量优化:搜索工具可以从正则中提取"内联"字面量,先快速扫描该字面量,找到候选行后再运行完整正则验证。这允许工具大量跳过不匹配的内容。
多重模式搜索:
- Teddy 算法(Intel Hyperscan 项目发明):使用 16 字节 SIMD 打包比较快速定位候选位置
- Aho-Corasick 算法:对大量字面量进行高效多模式匹配
- Commentz-Walter 算法:GNU grep 使用的多重模式搜索算法
4. 搜索策略:不要逐行搜索
最朴素的实现是逐行读取文件并逐行搜索,但这是反模式:
- 大多数文件不会有匹配,逐行搜索做了大量无意义的分行工作
- 每次搜索的启停开销巨大
正确的做法是搜索大块字节缓冲:
- 内存映射(mmap):将整个文件映射为连续内存
- 一次性读取:将整个文件读入内存
- 增量搜索:使用固定大小的中间缓冲区增量搜索
增量搜索虽然实现复杂(需要处理跨缓冲区行边界、上下文行保留等问题),但性能收益极为显著。
5. 输出打印
并行搜索时不能直接从工作线程打印结果(会导致输出交错)。解决方案:每个线程先写入中间缓冲区,再由主线程串行输出。
但当匹配大量结果时,中间缓冲区可能消耗大量内存。ripgrep 的优化:当搜索单个文件或输出被管道重定向时,直接写入输出,跳过中间缓冲区。
基准测试方法
测试环境
- AWS EC2 m4.2xlarge:Xeon E5-2680 2.8 GHz,16 GB 内存,80 GB SSD
- 本地机器:Intel i7-6900K 3.2 GHz,16 核,64 GB 内存
- 操作系统:Ubuntu 16.04
测试工具
| 工具 | 编写语言 | 特点 |
|---|---|---|
| ripgrep (rg) | Rust | 支持 .gitignore,默认递归搜索 |
| The Silver Searcher (ag) | C++ | 白名单模式,不支持 .gitignore |
| Universal Code Grep (ucg) | C++ | - |
| The Platinum Searcher (pt) | Go | 可选 .gitignore,默认搜索全部 |
| sift | Go | - |
| git grep | C | 使用 git 索引,不走目录树 |
| GNU grep | C | POSIX 标准工具 |
测试语料
- Linux 内核源代码树(编译过的,包含大量构建产物)
- 英文字幕:约 1 GB
- 俄语字幕:约 1.6 GB(用于测试 Unicode 支持)
测试流程
每个命令先运行 3 次预热(确保语料在页缓存中),再运行 10 次记录计时,最终结果以均值 ± 标准差呈现。
Linux 内核代码搜索基准测试
基准 1:默认设置下的字面量搜索
| 工具 | 用时(秒) |
|---|---|
| rg | 0.349 ± 0.104 |
| ag | 1.589 ± 0.009 |
| ucg | 0.218 ± 0.007* |
| pt | 0.462 ± 0.012 |
| sift | 0.352 ± 0.018 |
| git grep | 0.342 ± 0.005 |
ucg使用白名单模式(只搜索已知类型文件),pt和sift搜索所有文件(包括二进制和隐藏文件),rg和ag则跳过.gitignore中列出的文件。
要点:各工具的默认行为差异巨大,直接比较并不公平。但如果你希望得到最相关的结果,理解这些默认行为至关重要。
基准 2:公平对比(控制行为差异)
控制 .gitignore、行号显示和搜索策略后的结果:
| 工具 | 用时(秒) |
|---|---|
| rg (ignore) | 0.334 ± 0.053 |
| rg (ignore, mmap) | 1.611 ± 0.009 |
| ag (ignore, mmap) | 1.588 ± 0.011 |
| rg (whitelist) | 0.228 ± 0.042 |
| ucg (whitelist) | 0.218 ± 0.007* |
关键发现:内存映射在搜索大量小文件时比增量缓冲区慢得多。rg 的两种模式分别是 0.334s(增量缓冲区)和 1.611s(mmap),差距达 5 倍!
基准 3:大小写不敏感搜索
| 工具 | 用时(秒) |
|---|---|
| rg (ignore) | 0.345 ± 0.073 |
| pt (ignore) | 17.204 ± 0.126 |
| sift (ignore) | 0.805 ± 0.005 |
| git grep (ignore) | 0.343 ± 0.007 |
pt 的灾难性表现源自 Go 的正则库——它强制将模式转为 (?i) 标记后的 Go 正则,导致无法使用字面量优化。rg 则通过枚举所有大小写变体并利用 Teddy SIMD 算法保持高速。
基准 4:单词边界搜索 (-w)
| 工具 | 用时(秒) |
|---|---|
| rg (whitelist) | 0.220 ± 0.026* |
| ucg (whitelist) | 0.221 ± 0.007 |
| pt (ignore) | 14.417 ± 0.144 |
| sift (ignore) | 7.840 ± 0.123 |
pt 和 sift 再次因 Go 正则引擎而大幅降速。注意 rg 使用的是 Unicode 感知的单词边界,而其他工具仅支持 ASCII 单词边界。
基准 5:Unicode 感知模式
搜索包含 µ(微符号)的模式:
| 工具 | 用时(秒) |
|---|---|
| rg (ignore) Unicode | 0.355 ± 0.073 |
| git grep (ignore) | 13.045 ± 0.008 |
| git grep (ASCII) | 2.991 ± 0.004 |
| rg (whitelist, ASCII) | 0.225 ± 0.023* |
rg 在开启 Unicode 时几乎无性能损失,而 git grep 的 Unicode 模式比 ASCII 模式慢约 4 倍。奥秘在于 Rust 的正则库将 UTF-8 解码直接编译进了确定性有限状态机(DFA)中。
基准 6:带字面量后缀的正则
| 工具 | 用时(秒) |
|---|---|
| rg (whitelist) | 0.221 ± 0.022* |
| ucg (whitelist) | 0.301 ± 0.001 |
| git grep (ignore) | 1.108 ± 0.004 |
rg 和 ucg(通过 PCRE2)都能提取字面量后缀进行快速扫描。
基准 7-8:多字面量交替 + 大小写不敏感
搜索 ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT:
| 模式 | rg (ignore) | git grep (ignore) |
|---|---|---|
| 大小写敏感 | 0.351 ± 0.074 | 0.501 ± 0.003 |
| 大小写不敏感 | 0.391 ± 0.078 | 2.018 ± 0.006 |
rg 的 Teddy 算法在此大显身手——它完全跳过了正则引擎,直接使用 SIMD 多模式匹配。
基准 9-10:Unicode 字符类
搜索 \p{Greek}(所有希腊字符):
| 工具 | 用时(秒) |
|---|---|
| rg | 0.414 ± 0.021* |
| pt | 12.745 ± 0.166 |
| sift | 7.767 ± 0.264 |
大小写不敏感搜索 (?i)\p{Greek}:
| 工具 | 用时(秒) |
|---|---|
| rg | 0.425 ± 0.027 |
| sift | 0.002 ± 0.000(0 匹配:功能不支持) |
Rust 的 DFA 将 Unicode 字符类和大小写折叠规则全部编译进状态机中,因此能保持亚秒级响应。
基准 11:无字面量的纯正则
搜索 \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}:
| 工具 | 用时(秒) |
|---|---|
| rg (ASCII) | 0.416 ± 0.025* |
| ag (ASCII) | 2.339 ± 0.010 |
| sift | 25.563 ± 0.108 |
| git grep | 26.382 ± 0.044 |
没有字面量可提取时,所有工具都必须依赖正则引擎的原始性能。rg 的 DFA 比 Go、PCRE 和 GNU grep 的 DFA 更高效,关键优化之一是使用索引而非指针引用状态转换,避免了一次指针解引用。
单大文件搜索基准测试
基准 12:字面量搜索(英文/俄语)
| 工具 | 英文(秒) | 俄语(秒) |
|---|---|---|
| rg | 0.268 ± 0.000* | 0.325 ± 0.001* |
| grep | 0.516 ± 0.001 | 0.780 ± 0.001 |
| sift | 0.326 ± 0.002 | 16.418 ± 0.008 |
| pt | 3.433 ± 0.002 | 12.917 ± 0.009 |
rg 击败 GNU grep 的秘密在于选择更稀有的字节作为 Boyer-Moore 锚点。俄语语料中,UTF-8 编码的每个字符通常以 0xD0 或 0xD1 开头,Go 默认扫描首字节导致大量误报。rg 则使用频率表选择最佳字节,GNU grep 则依赖 Boyer-Moore 的最后一个字节(恰好也是稀有字节)。
基准 13:大小写不敏感搜索
| 工具 | 英文(秒) | 俄语(秒) |
|---|---|---|
| rg | 0.366 ± 0.001* | 1.131 ± 0.001* |
| grep (Unicode) | 4.084 ± 0.005 | 8.187 ± 0.006 |
| grep (ASCII) | 0.614 ± 0.001 | — |
rg 的 Unicode 大小写不敏感搜索甚至比 GNU grep 的 ASCII 模式还快!它通过枚举 Unicode 简单折叠规则下的所有字面量变体,送入 Teddy SIMD 算法。
基准 14-15:多字面量交替搜索
英文模式:Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty
| 工具 | 英文(秒) | 俄语(秒) |
|---|---|---|
| rg | 0.294 ± 0.001* | 1.300 ± 0.002* |
| grep | 2.955 ± 0.003 | 7.994 ± 0.017 |
大小写不敏感时:
| 工具 | 英文(秒) | 俄语(秒) |
|---|---|---|
| rg | 2.724 ± 0.002* | 4.834 ± 0.004* |
| ag (ASCII) | 5.170 ± 0.004 | 5.891 ± 0.001 |
Teddy 算法在处理大量大小写变体时遇到挑战——48 个字面量导致过多误报,此时 rg 优雅地回退到高级 Aho-Corasick 算法(使用内存连续的 DFA 转换表)。
基准 16:内联字面量优化
搜索 \w+ 周围上下文——一个刻意设计来打败前缀/后缀优化的模式:
| 工具 | 英文(秒) | 俄语(秒) |
|---|---|---|
| rg | 0.605 ± 0.000* | 0.957 ± 0.001* |
| grep | 1.286 ± 0.002 | 1.660 ± 0.002 |
只有 rg 和 GNU grep 实现了内联字面量优化。它们从模式中提取内部字面量,先快速扫描该字面量,找到候选行后再运行完整正则。这需要解析正则的抽象语法树(AST),对于大多数工具来说实现难度极高。Rust 的正则库为此暴露了 regex-syntax 库。
基准 17:无字面量纯正则(大文件)
| 工具 | 英文(秒) | 俄语(秒) |
|---|---|---|
| rg | 2.777 ± 0.003 | 4.905 ± 0.003 |
| grep (Unicode) | 超过 90 秒 | 超过 4 分钟 |
| grep (ASCII) | 4.411 ± 0.004 | — |
纯正则场景下,rg 的 DFA 实现通过将状态表示为转换表的索引(而非递进 ID),避免了一次乘法指令,仅用一个加法即可定位下个状态。
极限基准测试
基准 18:全匹配(搜索 .)
| 工具 | 用时(秒) | 匹配行数 |
|---|---|---|
| rg | 1.081 | 22,065,361 |
| ag | 1.660 | 55,939(错误!) |
| sift | 110.018 | 22,190,112 |
| pt | 0.245 | 3,027(错误!) |
ag 和 pt 有内置匹配数限制。rg 利用正则引擎的"是否匹配"短路优化,在每行开头立即找到匹配后退出。
基准 19:反向匹配(-v)
| 工具 | 用时(秒) |
|---|---|
| rg | 0.302 |
| ag | 需要数分钟 |
| git grep | 0.905 |
rg 在反向搜索场景下依然高效,而 ag 严重退化。
基准 20-21:上下文与超大文件
搜索 6.7 GB 超大文件的字面量:
| 工具 | 用时(秒) |
|---|---|
| rg | 1.786 |
| grep | 5.119 |
| ag (lines) | 19.132 |
ucg 在此测试中报告了错误的结果数——怀疑是无法处理 2GB 以上文件。
结论与关键技术
ripgrep 为何如此之快?
总结 ripgrep 在所有基准测试中击败竞争对手的关键优化技术:
文件发现与遍历:
- 最小化
stat调用的快速目录遍历 - 并行化
.gitignore规则匹配,支持一次性匹配多个 glob 模式 - 使用 Chase-Lev 工作窃取算法实现线程间快速任务分发
搜索与正则引擎: 4. 整体非常快的 Rust regex 正则引擎 5. 选择"稀有"字节作为 Boyer-Moore 锚点,利用预计算频率表 6. Teddy SIMD 算法用于快速多重模式搜索 7. 当 Teddy 不适用时,回退到高级 Aho-Corasick(每个输入字节仅一次转换表查询) 8. 将 UTF-8 解码直接编译进有限状态机
策略优化: 9. 使用增量缓冲区而非内存映射搜索大量小文件 10. 在搜索单个大文件时自动使用内存映射 11. 内联字面量提取,大量跳过不匹配内容
ripgrep 的正确性
除了性能,ripgrep 在正确性上也领先于竞争对手:
- 唯一提供完整
.gitignore支持且无 bug 的工具 - 唯一在不牺牲性能的前提下提供完整 Unicode 支持的工具
- 在反向匹配、上下文显示等边界场景中表现稳定
延伸阅读
- ripgrep GitHub 仓库
- Rust regex 库
- Hyperscan 项目 — Teddy 算法的来源
- Benchsuite 运行脚本 — 可复现本文所有基准测试
本文基于 Andrew Gallant 的博文翻译整理,内容遵循 UNLICENSE 和 MIT 许可证。
© 2026 四月 · CC BY-NC-SA 4.0
原文链接:https://aprilzz.com/tools/ripgrep-faster-search