工具推荐·阅读约 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