工具推荐·阅读约 8 分钟·
ripgrep:比 grep 快 10 倍的命令行搜索工具

ripgrep:比 grep 快 10 倍的命令行搜索工具

ripgrep (rg) 是一个用 Rust 编写的命令行搜索工具,比传统 grep 快 10 倍。本文深入分析其架构设计、正则引擎优化和 25 项基准测试,揭示它为何能同时实现极致性能和正确性。

原文来源:Andrew Gallant 的博客 — ripgrep 的作者对这款工具的深度技术剖析,包含 25 项详细的基准测试和性能分析。

概述

ripgrep(简称 rg)是一个用 Rust 编写的命令行搜索工具,集速度、跨平台(支持 Linux、macOS 和 Windows)和正确性于一身。

通过 25 项基准测试,我们可以支撑以下三大主张:

  1. 无论是搜索单个文件还是巨大的目录树,在性能或正确性上,没有其他搜索工具能打败 ripgrep。
  2. ripgrep 是唯一一个提供完整 Unicode 支持且不会为此牺牲性能的工具。
  3. 搜索多文件的工具通常被内存映射拖慢,而 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):

code
brew install ripgrep

从源码编译:

code
git clone git://github.com/BurntSushi/ripgrep
cd ripgrep
cargo build --release
./target/release/rg --version

启用 SIMD 加速(需 Rust nightly):

code
cargo build --release --features simd-accel

快速上手

code
# 递归搜索当前目录,遵守 .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线性时间复杂度保证,但功能较少

各工具使用的正则引擎:

工具正则引擎
ripgrepRust regex 库(有限自动机)
The Silver SearcherPCRE(回溯)
Universal Code GrepPCRE(回溯)
The Platinum SearcherGo regex(有限自动机)
siftGo regex(有限自动机)
GNU grep自研 DFA + 回退策略

回溯型引擎的一个典型问题:

code
import re
re.search('(a|aa|aaa)+b', 'a' * 100)  # 可能需要很长时间

3. 字面量优化

Boyer-Moore 算法:经典子串搜索算法,通过预计算的跳跃表跳过某些字符。在现代 CPU 上,关键在于多快能识别候选匹配——通常借助 SIMD 指令实现每秒数 GB 的吞吐量。

字面量提取:Rust 的正则库会从模式中提取字面量前缀/后缀。例如:

  • foo\w+bar → 提取 foobar
  • foo|bar → 提取 foobar 作为交替

内联字面量优化:搜索工具可以从正则中提取"内联"字面量,先快速扫描该字面量,找到候选行后再运行完整正则验证。这允许工具大量跳过不匹配的内容。

多重模式搜索

  • 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,默认搜索全部
siftGo-
git grepC使用 git 索引,不走目录树
GNU grepCPOSIX 标准工具

测试语料

  • Linux 内核源代码树(编译过的,包含大量构建产物)
  • 英文字幕:约 1 GB
  • 俄语字幕:约 1.6 GB(用于测试 Unicode 支持)

测试流程

每个命令先运行 3 次预热(确保语料在页缓存中),再运行 10 次记录计时,最终结果以均值 ± 标准差呈现。

Linux 内核代码搜索基准测试

基准 1:默认设置下的字面量搜索

工具用时(秒)
rg0.349 ± 0.104
ag1.589 ± 0.009
ucg0.218 ± 0.007*
pt0.462 ± 0.012
sift0.352 ± 0.018
git grep0.342 ± 0.005

ucg 使用白名单模式(只搜索已知类型文件),ptsift 搜索所有文件(包括二进制和隐藏文件),rgag 则跳过 .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

ptsift 再次因 Go 正则引擎而大幅降速。注意 rg 使用的是 Unicode 感知的单词边界,而其他工具仅支持 ASCII 单词边界。

基准 5:Unicode 感知模式

搜索包含 µ(微符号)的模式:

工具用时(秒)
rg (ignore) Unicode0.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

rgucg(通过 PCRE2)都能提取字面量后缀进行快速扫描。

基准 7-8:多字面量交替 + 大小写不敏感

搜索 ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT

模式rg (ignore)git grep (ignore)
大小写敏感0.351 ± 0.0740.501 ± 0.003
大小写不敏感0.391 ± 0.0782.018 ± 0.006

rg 的 Teddy 算法在此大显身手——它完全跳过了正则引擎,直接使用 SIMD 多模式匹配。

基准 9-10:Unicode 字符类

搜索 \p{Greek}(所有希腊字符):

工具用时(秒)
rg0.414 ± 0.021*
pt12.745 ± 0.166
sift7.767 ± 0.264

大小写不敏感搜索 (?i)\p{Greek}

工具用时(秒)
rg0.425 ± 0.027
sift0.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
sift25.563 ± 0.108
git grep26.382 ± 0.044

没有字面量可提取时,所有工具都必须依赖正则引擎的原始性能。rg 的 DFA 比 Go、PCRE 和 GNU grep 的 DFA 更高效,关键优化之一是使用索引而非指针引用状态转换,避免了一次指针解引用。

单大文件搜索基准测试

基准 12:字面量搜索(英文/俄语)

工具英文(秒)俄语(秒)
rg0.268 ± 0.000*0.325 ± 0.001*
grep0.516 ± 0.0010.780 ± 0.001
sift0.326 ± 0.00216.418 ± 0.008
pt3.433 ± 0.00212.917 ± 0.009

rg 击败 GNU grep 的秘密在于选择更稀有的字节作为 Boyer-Moore 锚点。俄语语料中,UTF-8 编码的每个字符通常以 0xD00xD1 开头,Go 默认扫描首字节导致大量误报。rg 则使用频率表选择最佳字节,GNU grep 则依赖 Boyer-Moore 的最后一个字节(恰好也是稀有字节)。

基准 13:大小写不敏感搜索

工具英文(秒)俄语(秒)
rg0.366 ± 0.001*1.131 ± 0.001*
grep (Unicode)4.084 ± 0.0058.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

工具英文(秒)俄语(秒)
rg0.294 ± 0.001*1.300 ± 0.002*
grep2.955 ± 0.0037.994 ± 0.017

大小写不敏感时:

工具英文(秒)俄语(秒)
rg2.724 ± 0.002*4.834 ± 0.004*
ag (ASCII)5.170 ± 0.0045.891 ± 0.001

Teddy 算法在处理大量大小写变体时遇到挑战——48 个字面量导致过多误报,此时 rg 优雅地回退到高级 Aho-Corasick 算法(使用内存连续的 DFA 转换表)。

基准 16:内联字面量优化

搜索 \w+ 周围上下文——一个刻意设计来打败前缀/后缀优化的模式:

工具英文(秒)俄语(秒)
rg0.605 ± 0.000*0.957 ± 0.001*
grep1.286 ± 0.0021.660 ± 0.002

只有 rg 和 GNU grep 实现了内联字面量优化。它们从模式中提取内部字面量,先快速扫描该字面量,找到候选行后再运行完整正则。这需要解析正则的抽象语法树(AST),对于大多数工具来说实现难度极高。Rust 的正则库为此暴露了 regex-syntax 库。

基准 17:无字面量纯正则(大文件)

工具英文(秒)俄语(秒)
rg2.777 ± 0.0034.905 ± 0.003
grep (Unicode)超过 90 秒超过 4 分钟
grep (ASCII)4.411 ± 0.004

纯正则场景下,rg 的 DFA 实现通过将状态表示为转换表的索引(而非递进 ID),避免了一次乘法指令,仅用一个加法即可定位下个状态。

极限基准测试

基准 18:全匹配(搜索 .

工具用时(秒)匹配行数
rg1.08122,065,361
ag1.66055,939(错误!)
sift110.01822,190,112
pt0.2453,027(错误!)

agpt 有内置匹配数限制。rg 利用正则引擎的"是否匹配"短路优化,在每行开头立即找到匹配后退出。

基准 19:反向匹配(-v

工具用时(秒)
rg0.302
ag需要数分钟
git grep0.905

rg 在反向搜索场景下依然高效,而 ag 严重退化。

基准 20-21:上下文与超大文件

搜索 6.7 GB 超大文件的字面量:

工具用时(秒)
rg1.786
grep5.119
ag (lines)19.132

ucg 在此测试中报告了错误的结果数——怀疑是无法处理 2GB 以上文件。

结论与关键技术

ripgrep 为何如此之快?

总结 ripgrep 在所有基准测试中击败竞争对手的关键优化技术:

文件发现与遍历:

  1. 最小化 stat 调用的快速目录遍历
  2. 并行化 .gitignore 规则匹配,支持一次性匹配多个 glob 模式
  3. 使用 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 支持的工具
  • 在反向匹配、上下文显示等边界场景中表现稳定

延伸阅读


本文基于 Andrew Gallant 的博文翻译整理,内容遵循 UNLICENSE 和 MIT 许可证。

分享到
微博Twitter

© 2026 四月 · CC BY-NC-SA 4.0

原文链接:https://aprilzz.com/tools/ripgrep-faster-search