首页
/ ripgrep benchsuite 基准测试实录:2020 年 Arch Linux 环境下 rg、grep、ag、git grep 与 ugrep 的对比方法与结果解读

ripgrep benchsuite 基准测试实录:2020 年 Arch Linux 环境下 rg、grep、ag、git grep 与 ugrep 的对比方法与结果解读

2026-09-04 22:25:52作者:牧宁李

本文以 ripgrep 仓库中 2020-10-14-archlinux-frink 基准运行记录 为主体,完整还原一次官方基准测试的执行命令、工具版本矩阵与构建方式,并结合 benchsuite 脚本 的源码,讲清楚测试语料、18 组基准场景的设计动机、统计方法与公平性处理。读完后你既能复现这套基准测试,也能看懂 summaryraw.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 defconfigmake -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 解释 \wbench_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 脚本 的实现可以看到多处为保证可比性做的处理:

  1. locale 显式控制grep 的性能受 locale 影响巨大(脚本注释称“启用 Unicode 有相当显著的性能影响”),因此为 grep/git grep 显式设置 LC_ALL=C(ASCII 模式)或 LC_ALL=en_US.UTF-8(Unicode 模式),并在结果中用不同标签区分,例如 grep (ASCII)grep。这些环境变量会写入 raw.csvenv 列,可追溯。
  2. ugrep 的 -a 特例。俄语语料基准中 ugrep 会错误地把纯 UTF-8 的 ru.txt 判定为二进制而跳过,脚本改用 ugrep -a ... 强制按文本处理,注释坦承这“技术上给了 ugrep 一点优势,因为它不用再检查二进制数据了”(bench_subtitles_ru_literal)。
  3. 输出统一丢弃。所有命令 stderr 指向 DEVNULL;启用行计数时 stdoutPIPE 统计换行数,否则丢弃。行计数用于校验各工具“找到的结果是否一致”——如果某工具 lines 为 0(如 summarysubtitles_ru_alternate_casei 场景下 ag 与 ugrep 的 lines: 0),说明它在该场景下没有匹配到任何内容,其耗时数字没有可比意义。
  4. 同一工具可出现多次Command 允许同一搜索工具以不同参数名参测,如 rgrg (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),这类结果虽快但无实际意义,阅读基准时必须同时看耗时与行数。
  • 同一工具不同变体的对比(rg vs rg (mmap)rg vs rg (ASCII))展示了配置项的量化代价,例如 Linux 语料上 rg -n --mmap 约 1.34 秒,明显慢于默认读法约 0.13 秒。

如何复现这套基准测试

结合 benchsuite 脚本 的参数与 main 流程,一次完整的复现路径是:

  1. 准备语料(幂等,可断点续传):

    $ ./benchsuite --dir /tmp/benchsuite --download linux subtitles-en subtitles-ru
    

    注意脚本帮助文本的警告:all 会下载超过 1 GB 的压缩数据、解压后约 13 GB,且包含完整构建 Linux 内核(make defconfig && make -j$(nproc))。磁盘与编译时间需预留。

  2. 查看可用基准

    $ ./benchsuite --dir /tmp/benchsuite --list
    
  3. 执行并落盘原始数据(与 2020 年记录一致的参数组合):

    $ ./benchsuite \
          --dir /tmp/benchsuite \
          --raw runs/<日期>-<机器名>/raw.csv \
          --warmup-iter 1 \
          --bench-iter 5 \
          | tee runs/<日期>-<机器名>/summary
    
  4. 只跑部分基准:追加位置参数正则,如 ./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 与二进制识别等公平性处理,都写在脚本源码里可逐行核对。对想要量化对比搜索工具、或学习“如何设计公平基准”的读者,这套文件本身就是完整的参考实现。

登录后查看全文
热门项目推荐
相关项目推荐