ripgrep benchsuite 基准测试实录:2020 年 Arch Linux 环境下 rg、grep、ag、git grep 与 ugrep 的对比方法与结果解读
【免费下载链接】ripgrepripgrep recursively searches directories for a regex pattern while respecting your gitignore项目地址: https://gitcode.com/GitHub_Trending/ri/ripgrep
本文以 ripgrep 仓库中 2020-10-14-archlinux-frink 基准运行记录 为主体,完整还原一次官方基准测试的执行命令、工具版本矩阵与构建方式,并结合 benchsuite 脚本 的源码,讲清楚测试语料、18 组基准场景的设计动机、统计方法与公平性处理。读完后你既能复现这套基准测试,也能看懂 summary 与 raw.csv 中每个数字的来源。
一次基准运行的完整记录
benchsuite/runs/2020-10-14-archlinux-frink/README.md 记录的是 2020 年 10 月 14 日在一台 Arch Linux 机器(代号 "frink")上采集的基准数据。运行方式是从仓库根目录执行benchsuite/benchsuite脚本,具体命令为:
$ ./benchsuite \ --dir /tmp/benchsuite \ --raw runs/2020-10-14-archlinux-frink/raw.csv \ --warmup-iter 1 \ --bench-iter 5各参数的含义(依据 benchsuite 脚本参数定义):
| 参数 | 取值 | 作用 |
|---|---|---|
--dir | /tmp/benchsuite | 存放语料并执行搜索的目录,脚本会自动创建 |
--raw | runs/.../raw.csv | 将所有原始样本以 CSV 格式落盘,便于事后复算 |
--warmup-iter | 1 | 每条命令正式计时前的预热运行次数(默认 1) |
--bench-iter | 5 | 每条命令实际计时的采样次数(默认 3,此处加倍以提高稳定性) |
与 2022 年 12 月的新一轮运行记录 相比,本次运行把结果同时输出到终端并另存为summary文件,且采样迭代数从默认值提高到 5 次。
参与对比的工具及其版本
基准测试的结论是否可比,前提是记录清楚每个参赛工具的版本。该记录文档固定了如下版本矩阵:
$ rg --version ripgrep 12.1.1 (rev def993bad1) -SIMD -AVX (compiled) +SIMD +AVX (runtime) $ grep -V grep (GNU grep) 3.4 $ ag -V ag version 2.2.0 Features: +jit +lzma +zlib $ git --version git version 2.28.0 $ ugrep --version ugrep 3.0.2 x86_64-pc-linux-gnu +avx2 +pcre2_jit +zlib +bzip2 +lzma +lz4其中 ripgrep 并非使用发行版二进制,而是从源码在提交def993bad1上以发布模式、启用 PCRE2 特性编译:
$ cargo build --release --features 'pcre2'这一点对结果有直接影响:rg --version输出中-SIMD -AVX (compiled)表示编译期未固化 SIMD 指令集,+SIMD +AVX (runtime)表示运行时检测到了 AVX 能力并动态启用,因此同一二进制可适配不同 CPU。而--features 'pcre2'则对应仓库中独立的 pcre2 匹配器 crate,用于提供 PCRE2 正则能力(-P选项)。
测试语料:一大文件集与一大单文件
benchsuite 脚本 开头注释点明了设计思路:构造“少量大文件”与“大量小文件”两类截然不同、性能特征与相关策略都不同的语料。具体有三类,通过--download参数拉取(脚本自带幂等下载逻辑):
- linux:浅克隆一份 Linux 内核源码,并执行
make defconfig加make -j$(nproc)完整构建。源码注释解释(download_linux):构建过程会在仓库里产生大量“搜索工具本不该触碰的垃圾文件”(如二进制、.o等),这正好考察工具在 gitignore/二进制识别下的过滤行为。克隆特意使用--depth 1浅克隆,既保证语料一致又降低成本。 - subtitles-en:OPUS-OpenSubtitles 2016 英文版字幕,解压后截取前 5500 万行生成
en.sample.txt,使规模与俄语语料相当、基准能在合理时间跑完(download_subtitles_en)。 - subtitles-ru:俄语完整版字幕
ru.txt,用于考察 Unicode 匹配能力。
--download的可选值为all, linux, subtitles-en, subtitles-ru;脚本帮助文本明确警告:选择all时解压后总量约 13 GB,且包含构建 Linux 内核的过程。
基准场景设计:18 组测试覆盖了哪些维度
脚本通过命名约定自动发现基准:collect_benchmarks 遍历所有以bench_开头的函数,按定义顺序执行,并支持用位置参数正则过滤只跑部分基准。本目录raw.csv中出现的场景可归纳为四条主线:
Linux 内核语料:代码搜索类场景
| 场景名 | 模式 | 考察点 |
|---|---|---|
linux_literal_default | PM_RESUME | 各工具默认设置下的表现。源码注释直言这是一个“刻意为之的不公平基准”:ugrep 与 grep 默认不做智能过滤,会搜索比 rg、ag、git grep 更多的文件,但它教学价值高,能展示默认行为的差异(bench_linux_literal_default) |
linux_literal | PM_RESUME | “尽量公平”的字面量搜索:用所有工具都支持的最少选项,例如强制都输出行号 |
linux_literal_casei | PM_RESUME | 大小写不敏感匹配(加-i) |
linux_re_literal_suffix | [A-Z]+_RESUME | 模式内含字面量后缀的正则,考察各引擎对“前缀可变 + 后缀固定”字面量的利用 |
linux_word | PM_RESUME | -w整词匹配 |
linux_alternates/linux_alternates_casei | ERR_SYS\|PME_TURN_OFF\|LINK_REQ_RST\|CFG_BME_EVT | 小规模字面量交替(OR),考察交替字面量优化 |
linux_unicode_greek(_casei) | \p{Greek} | Unicode 类别匹配,仅 rg 与 ugrep 参测 |
linux_unicode_word | \wAh | 考察\w的 Unicode 语义:源码注释指出只有 ripgrep 和LC_ALL=en_US.UTF-8下的 git grep 能“答对”,其余工具按 ASCII 解释\w(bench_linux_unicode_word) |
linux_no_literal | \w{5}\s+\w{5}... | 刻意构造的无任何字面量的正则,让所有字面量加速手段失效,属于最坏情况压力测试 |
字幕语料:大单文件场景
subtitles_en_*与subtitles_ru_*两组各 7 个场景,在单个超大文本文件上重复考察上述维度(字面量、大小写不敏感、整词、交替、内嵌字面量的复合正则、无字面量正则)。英文语料用 5500 万行的样本文件,俄语语料用完整ru.txt。例如subtitles_ru_surrounding_words使用模式\w+\s+Холмс\s+\w+,考察在 Unicode 文本中“字面量前后再加词”这类真实检索需求。
公平性细节:环境、locale 与特殊选项
从 benchsuite 脚本 的实现可以看到多处为保证可比性做的处理:
- locale 显式控制。
grep的性能受 locale 影响巨大(脚本注释称“启用 Unicode 有相当显著的性能影响”),因此为 grep/git grep 显式设置LC_ALL=C(ASCII 模式)或LC_ALL=en_US.UTF-8(Unicode 模式),并在结果中用不同标签区分,例如grep (ASCII)与grep。这些环境变量会写入raw.csv的env列,可追溯。 - ugrep 的
-a特例。俄语语料基准中 ugrep 会错误地把纯 UTF-8 的ru.txt判定为二进制而跳过,脚本改用ugrep -a ...强制按文本处理,注释坦承这“技术上给了 ugrep 一点优势,因为它不用再检查二进制数据了”(bench_subtitles_ru_literal)。 - 输出统一丢弃。所有命令
stderr指向DEVNULL;启用行计数时stdout走PIPE统计换行数,否则丢弃。行计数用于校验各工具“找到的结果是否一致”——如果某工具 lines 为 0(如 summary 中subtitles_ru_alternate_casei场景下 ag 与 ugrep 的lines: 0),说明它在该场景下没有匹配到任何内容,其耗时数字没有可比意义。 - 同一工具可出现多次。
Command允许同一搜索工具以不同参数名参测,如rg、rg (mmap)、rg (ASCII),分别对应对比--mmap强制内存映射、以及(?-u)关闭 Unicode 语义的降级行为。
统计方法与输出格式
每个基准的执行流程(Benchmark.run / run_one):先跑warmup_iter次预热(不落数),再跑bench_iter次采样,记录每次的duration(秒,含小数毫秒)与line_count。汇总时(main 输出段):
- 对每条命令计算均值 ± 标准差(
statistics.mean/stdev),并记录输出行数; - 星号
*标记两种“最快”:按分布均值最快的命令、以及单次最快样本所属的命令; - 所有原始样本写入
--raw指定的 CSV,字段为benchmark, warmup_iter, iter, name, command, duration, lines, env。
因此 raw.csv 中每行是一条原始测量,例如linux_literal_default场景下rg PM_RESUME的 5 次采样约在 0.119–0.129 秒之间,而grep -r PM_RESUME ./的 5 次采样在 1.14–1.15 秒之间;summary则给出聚合结果。
2020 年 frink 机器上的关键结果
以下数据取自 summary(时间为均值 ± 标准差,单位秒;*为该场景最快):
Linux 内核语料(代码搜索)
| 场景 | rg | ugrep | git grep | ag | grep |
|---|---|---|---|---|---|
| literal_default | 0.124* | 0.136 | 0.480 | 0.771 | 1.147 |
| literal | 0.130* | 0.309 | 0.464 | 0.880 | — |
| literal_casei | 0.131* | 0.288 | 0.482 | 0.657 | — |
| re_literal_suffix | 0.126* | 0.548 | 1.217 | 1.044 | — |
| alternates_casei | 0.226* | 0.275 | 0.977 | 0.700 | — |
| unicode_word | 0.140 | 0.276 | 8.188 (2.334 ASCII) | — | — |
| no_literal | 0.402 (0.254 ASCII) * | 0.363 ASCII | 14.591 | 0.934 ASCII | — |
字幕语料(大单文件)
| 场景 | rg | ugrep | git grep | ag | grep |
|---|---|---|---|---|---|
| subtitles_en_literal | 0.226* | 0.404 | — | 2.547 | 0.800 |
| subtitles_en_literal_casei | 0.398* | 1.103 | — | 2.595 | 3.621 (0.938 ASCII) |
| subtitles_ru_literal | 0.215* | 1.841 | — | 2.704 | 0.748 |
| subtitles_ru_literal_casei | 0.484* | 1.835 | — | 0.623 | 6.709 (0.732 ASCII) |
| subtitles_en_surrounding_words | 0.335* | 70.234 | — | 7.418 | 1.764 |
| subtitles_ru_surrounding_words | 0.310* | 70.802 | — | — | 1.419 |
| subtitles_ru_no_literal | 3.098 (2.728 ASCII) * | 1.193 ASCII | — | 1.902 ASCII | 1.758 ASCII |
几点值得注意的结构性结论(均由 summary 数据直接支撑):
- 在绝大多数场景中 rg 是均值最快者;ugrep 在纯 ASCII 字面量、整词匹配及“无字面量正则(ASCII 模式)”等少数场景下略快或相当。
- 模式中的字面量是最大变量:
no_literal场景中各工具耗时普遍翻数倍——Linux 语料上 rg 从 0.13 秒升到 0.25–0.40 秒,git grep 从 0.46 秒升到 3.18–14.59 秒;单文件场景下 ugrep 的 Unicode 模式甚至达到 24–70 秒量级,而 ASCII 模式回落至 1–5 秒,印证了字面量加速与 Unicode 处理路径对性能的影响。 - 正确性优先于速度:
lines列暴露了多个工具在 Unicode 场景的“零匹配”(如subtitles_ru_alternate_casei中 ag/ugrep 的lines: 0),这类结果虽快但无实际意义,阅读基准时必须同时看耗时与行数。 - 同一工具不同变体的对比(
rgvsrg (mmap)、rgvsrg (ASCII))展示了配置项的量化代价,例如 Linux 语料上rg -n --mmap约 1.34 秒,明显慢于默认读法约 0.13 秒。
如何复现这套基准测试
结合 benchsuite 脚本 的参数与main流程,一次完整的复现路径是:
准备语料(幂等,可断点续传):
$ ./benchsuite --dir /tmp/benchsuite --download linux subtitles-en subtitles-ru注意脚本帮助文本的警告:
all会下载超过 1 GB 的压缩数据、解压后约 13 GB,且包含完整构建 Linux 内核(make defconfig && make -j$(nproc))。磁盘与编译时间需预留。查看可用基准:
$ ./benchsuite --dir /tmp/benchsuite --list执行并落盘原始数据(与 2020 年记录一致的参数组合):
$ ./benchsuite \ --dir /tmp/benchsuite \ --raw runs/<日期>-<机器名>/raw.csv \ --warmup-iter 1 \ --bench-iter 5 \ | tee runs/<日期>-<机器名>/summary只跑部分基准:追加位置参数正则,如
./benchsuite --dir /tmp/benchsuite linux_unicode,仅运行名称匹配linux_unicode的场景;若某些工具未安装,加--allow-missing跳过缺失命令而不是报错(MissingCommands异常由 raise_if_missing 触发);用--disabled a,b可显式排除命令。
复现时建议同时记录各工具--version输出、CPU/内存规格与 ripgrep 的构建提交号——这正是该目录 README 固定下来的三要素(命令、版本、构建方式),缺少任何一项,结果都无法与仓库中历次记录(如 2018 年、2022 年)横向对照。
小结
2020-10-14-archlinux-frink 记录 虽然篇幅不长,但它与 benchsuite 脚本、raw.csv 原始样本 和 summary 汇总 共同构成了一份可验证、可复现的基准测试样本:执行命令、工具版本矩阵、构建方式被完整固化;语料构造(“大量小文件”的内核树 + “少量大文件”的字幕集)、18 组覆盖字面量/Unicode/无字面量等维度的场景、warmup 加多次采样的均值±标准差统计、以及 locale 与二进制识别等公平性处理,都写在脚本源码里可逐行核对。对想要量化对比搜索工具、或学习“如何设计公平基准”的读者,这套文件本身就是完整的参考实现。
【免费下载链接】ripgrepripgrep recursively searches directories for a regex pattern while respecting your gitignore项目地址: https://gitcode.com/GitHub_Trending/ri/ripgrep
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考