ripgrep benchsuite 实战:在 i9-12900K 上复现 2022-12-16 命令行搜索工具基准测试并读懂结果
2026/9/5 21:00:12 网站建设 项目流程

ripgrep benchsuite 实战:在 i9-12900K 上复现 2022-12-16 命令行搜索工具基准测试并读懂结果

【免费下载链接】ripgrepripgrep recursively searches directories for a regex pattern while respecting your gitignore项目地址: https://gitcode.com/GitHub_Trending/ri/ripgrep

ripgrep 仓库内置了一个名为 benchsuite 的基准测试框架,用于在真实语料上系统对比 ripgrep(rg)、GNU grep、ag、git grep、ugrep 等命令行搜索工具的性能与正确性。本文以仓库中 2022-12-16 在 Arch Linux(Intel i9-12900K、128GB 内存)上完成的基准运行记录为核心,完整还原当时的测试命令、工具版本与硬件环境,并结合 benchsuite 脚本 的源码讲清楚测试集如何设计、数据如何采集与汇总,帮助你独立复现这套测试、并正确解读 summary 中的每一组数字。

一、这次基准运行是什么:命令、环境与工具版本

本次运行的说明文档 记录了完整的采集方式。基准数据于 2022-12-16 采集,通过仓库根目录的benchsuite/benchsuite脚本运行,实际执行的命令是:

$ ./benchsuite \ --dir /dev/shm/benchsuite \ --raw runs/2022-12-16-archlinux-duff/raw.csv \ | tee runs/2022-12-16-archlinux-duff/summary

这条命令有三个要点:

  • --dir /dev/shm/benchsuite:语料目录放在/dev/shm(tmpfs 内存盘)中,避免磁盘 I/O 波动干扰计时;
  • --raw .../raw.csv:把每一次采样的原始数据(所有迭代、所有命令的耗时与命中行数)以 CSV 格式落盘,即 raw.csv;
  • 标准输出经tee同时打印并保存为 summary,即按基准项分组、展示“均值 ± 标准差”的汇总表。

参与对比的工具版本(摘自说明文档):

工具版本备注
ripgrep13.0.0 (rev 87c4a2b4b1)编译时未启用 SIMD/AVX(-SIMD -AVX (compiled)),运行时探测启用(+SIMD +AVX (runtime)
GNU grep3.8
ag2.2.0+jit +lzma +zlib
git2.39.0git grep形式参与
ugrep3.9.2+avx2 +pcre2jit +zlib +bzip2 +lzma +lz4 +zstd

ripgrep 使用的二进制并非发行版预编译包,而是从 commit 7f23cd63 源码编译:

$ cargo build --release --features 'pcre2'

关于pcre2feature,当前仓库的 Cargo.toml 中可见其定义:pcre2 = ["grep/pcre2"],即通过 crates/grep 启用可选依赖 crates/pcre2(一个封装 Rustpcre2crate 的 grep matcher)。这意味着基准测试里的 rg 支持 PCRE2 兼容语法,与很多发行版打包默认配置保持一致。

运行环境为 Arch Linux,硬件是 Intel i9-12900K 处理器、128GB 内存。目录名中的 “duff” 是这台机器的代号——仓库里更早一次 2020-10-14 的运行(frink,ripgrep 12.1.1、grep 3.4、git 2.28)见 benchsuite/runs/2020-10-14-archlinux-frink/README.md,可以跨机器、跨版本对照阅读。

二、benchsuite 脚本的工作机制

benchsuite/benchsuite 是一个纯 Python 脚本(#!/usr/bin/env python3,无第三方依赖),它定义了三个核心对象:

  • Benchmark:一组用于相互比较的命令;
  • Command:单个被测命令(参数直接透传给subprocess.run,但 stdin/stdout/stderr 由框架统一接管);
  • Result:一次基准运行的全部采样,负责统计均值与标准差。

命令行参数

脚本main()中的 argparse 定义(benchsuite/benchsuite)支持以下常用选项:

参数作用
--dir PATH语料目录,也是所有搜索命令的工作目录(默认当前目录)
--download CORPUS下载并准备语料后退出;可选alllinuxsubtitles-ensubtitles-ru。注意all解压后总计约 13GB,且包含完整编译 Linux 内核
--raw PATH把所有采样原始数据写成 CSV
--warmup-iter N每个命令正式采样前的预热次数(默认 1)
--bench-iter N每个命令正式采样次数(默认 3)
--allow-missing允许某些被测命令缺失时跳过对应项继续运行
--disabled a,b逗号分隔的命令名列表,直接跳过
--list只列出可用基准项名称
--force允许覆盖已存在的--raw输出文件
bench PAT位置参数,正则过滤只运行匹配的基准项

collect_benchmarks(benchsuite/benchsuite)通过扫描全局命名空间中以bench_开头的函数来动态发现基准项,因此新增一个基准只需要新增一个同名函数。缺语料的基准项会打印missing: ...提示并跳过;缺命令的基准项则提示可用--allow-missing降级运行。

语料准备

脚本定义了两大性质完全不同的语料(源码注释原文:一个是“少量大文件”,一个是“大量小文件”,两者在性能特征和相关性策略上差异巨大):

  1. linux 语料:浅克隆 Linux 内核源码树(git clone --depth 1),随后执行make defconfigmake -j$(nproc)完整构建内核(download_linux)。构建会往仓库里产生大量编译产物,这些“垃圾文件”正是搜索工具默认应该跳过的对象。vmlinux二进制文件存在与否被用作语料就绪的标志。
  2. subtitles 语料:从 OPUS-OpenSubtitles 下载英、俄双语单语文本并解压;英文语料还会截取前 5500 万行生成en.sample.txt,使英文基准的规模与俄语语料接近(download_subtitles_en)。

采样与统计

Benchmark.run()对每个命令先执行warmup_count次预热(本次运行为 1 次),再执行count次正式采样(本次运行为 3 次)(benchsuite/benchsuite)。每次采样记录两个量:

  • duration:命令墙钟耗时(秒,含小数毫秒);
  • line_count:搜索结果输出的行数(通过统计 stdout 中\n计数得到)。

行数的作用不只是统计——它是正确性交叉验证:同一基准下各工具的行数应当一致(除非命令的语义本身不同,例如 ASCII 与 Unicode 的\w定义不同)。Result.distribution_forstatistics.meanstatistics.stdev计算每组采样的均值与标准差,即 summary 中0.084 +/- 0.002这类数字的来源。

raw.csv 的格式

--raw输出的 CSV 固定包含 8 个字段(benchsuite/benchsuite):

benchmark,warmup_iter,iter,name,command,duration,lines,env

本次运行的 raw.csv 共 400 行(1 行表头 + 399 条采样),例如:

linux_literal_default,1,3,rg,rg PM_RESUME,0.08678817749023438,39, linux_literal_default,1,3,rg,rg PM_RESUME,0.08307123184204102,39, linux_literal_default,1,3,rg,rg PM_RESUME,0.08347964286804199,39, linux_literal_default,1,3,ag,ag PM_RESUME,0.2955434322357178,39,

其中env字段记录该命令附加的环境变量(如LC_ALL=C),这让每条数据都能被完整复现。

三、基准用例设计:从“故意不公平”到“尽量公平”

所有 linux 系列基准都在构建好的内核源码树上执行;subtitles 系列针对单个大文本文件。下面按脚本源码逐个说明(函数定义见 benchsuite/benchsuite)。

linux 系列(语料:Linux 内核源码树)

基准名模式设计意图
linux_literal_defaultPM_RESUME故意不公平:各工具都用默认参数。rg/ag/git grep 默认会做智能过滤(跳过隐藏/二进制文件、遵循 .gitignore),而 grep/ugrep 默认不做,所以后者必然搜更多文件。注释明确说它“pedagogically useful”——用来说明默认行为的差异
linux_literalPM_RESUME尽量公平:所有工具使用“各工具都有的最小参数集”,如-n输出行号,统一不做忽略大小写
linux_literal_caseiPM_RESUME+-i忽略大小写的字面量搜索
linux_re_literal_suffix[A-Z]+_RESUME字面量嵌在正则里,考察正则引擎处理“前缀正则 + 后缀字面量”的能力
linux_wordPM_RESUME+-w全词匹配(word boundary)
linux_unicode_greek\p{Greek}Unicode 属性类匹配(仅 rg 与 ugrep 参与)
linux_unicode_greek_casei\p{Greek}+-i忽略大小写的 Unicode 属性类。源码注释直言“Only ripgrep gets this right (and it's still fast)”
linux_unicode_word\wAh考察\w的 Unicode 感知。注释指出只有 ripgrep 和设置了LC_ALL=en_US.UTF-8的 git grep 才是真的按 Unicode 处理,其余用 ASCII 语义
linux_no_literal\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}不含任何字面量的正则,击败所有“字面量快捷优化”(memmem 预过滤等),注释承认“applicability is somewhat suspicious”
linux_alternates/linux_alternates_caseiERR_SYS\|PME_TURN_OFF\|LINK_REQ_RST\|CFG_BME_EVT短字面量 alternation,无公共前缀字节,考察多字面量优化

各工具在同一基准中的典型调用方式(以linux_literal为例):

rg -n PM_RESUME # rg rg -n --mmap PM_RESUME # rg (mmap) ag -s PM_RESUME # ag (mmap),ag 默认内存映射 git grep -I -n PM_RESUME # git grep,LC_ALL=C ugrep -r --ignore-files --no-hidden -I -n PM_RESUME ./ # ugrep

可以看到脚本为每个工具精心选择了“等价能力”的参数:git grep-I(跳过二进制)、ugrep--ignore-files --no-hidden -Iag-s(静默)、rg 单独列出--mmap变体以便对照 mmap 与 read 两种读取策略。对 grep/git grep 还显式设置LC_ALL:脚本顶部注释解释,grep 的 locale 设置对性能影响巨大(GREP_ASCII = {'LC_ALL': 'C'}GREP_UNICODE = {'LC_ALL': 'en_US.UTF-8'}),所以同一工具常以 ASCII/Unicode 两种环境成对出现。

subtitles 系列(语料:OpenSubtitles 大文本)

英文(en.sample.txt)与俄语(ru.txt,西里尔文)各 7 个基准,模式围绕“Sherlock Holmes / Шерлок Холмс”展开:

基准名模式考察点
*_literalSherlock Holmes/Шерлок Холмс纯字面量,含rgrg (no mmap)对照
*_literal_casei同上 +-i忽略大小写
*_literal_word字面量 + 词边界对俄语基准,脚本手工拼接(?-u:^|\W)...(?-u:$|\W)来模拟 ASCII 词边界,因为\b无法在禁用 Unicode 的模式里使用
*_alternate5 个人名的 alternation多字面量选择
*_alternate_casei同上 +-i多字面量 + 忽略大小写
*_surrounding_words\w+\s+Holmes\s+\w+含内部字面量的复杂正则
*_no_literal\w{5}\s+\w{5}...(7 组)无字面量正则。注释特别说明:带 Unicode 支持的 grep 在 2 分钟时仍无完成迹象,被作者手动终止,故该基准只跑 ASCII 变体

俄语基准还有两处值得注意的“公平性补丁”(源码注释):ugrep 会错误地把全合法的 UTF-8 俄语语料识别为二进制文件,因此给 ugrep 加-a强制按文本处理——注释坦承这“technically gives it an edge”;ag (lines) (ASCII)之类的变体则反映 ag 只按 ASCII 解释\w,对西里尔文本匹配 0 行的事实会被 line count 如实记录。

四、summary 汇总格式与星号含义

主循环把每个基准的结果打印成如下格式(benchsuite/benchsuite):

<benchmark name> (pattern: <pattern>) ---------------------------------- <name padded> <mean> +/- <stdev> (lines: <n>)*[fastest]

两个*含义不同:

  • 名字后的*:该命令产出了全场最快的单次采样fastest_sample);
  • 行末的*:该命令的均值最低fastest_cmd,按分布判定)。

同一基准内各命令的(lines: N)应当一致;出现分歧往往意味着结果语义或正确性不同(见下文实例)。

五、2022-12-16 运行结果解读

以下数据全部来自 summary,单位秒。

内核语料上的字面量搜索(linux_literal_default,默认参数、故意不公平):

命令均值 ± 标准差lines
rg0.084 ± 0.002 *39
ugrep0.105 ± 0.00239
git grep0.225 ± 0.00739
ag0.295 ± 0.00139
grep0.996 ± 0.00339

公平参数版(linux_literal)中 rg 与自身 mmap 变体的对照:

命令均值 ± 标准差
rg0.085 ± 0.001
rg (mmap)0.322 ± 0.002
ag (mmap)0.290 ± 0.002
git grep0.211 ± 0.009
ugrep0.189 ± 0.005

一个直观结论:在此机器与语料上,rg默认(read 路径)显著快于显式--mmap,而 ag 因默认即 mmap,其速度与 rg 的 mmap 变体相近。

Unicode 相关基准最能体现引擎差异。linux_unicode_word(模式\wAh)中,git grep(Unicode 环境)耗时 3.980s(±0.241),而 rg 仅 0.085s;linux_no_literal中差距更悬殊:

命令均值 ± 标准差lines
rg (ASCII)0.200 ± 0.001720
ugrep (ASCII)0.236 ± 0.003722
rg0.266 ± 0.006721
ugrep3.403 ± 0.008723
git grep (ASCII)2.144 ± 0.014720
git grep7.346 ± 0.017721
ag (ASCII)0.832 ± 0.0071134

注意ag (ASCII)的 lines 为 1134,与其他工具约 720 不一致——ASCII 语义的\w与 Unicode 语义匹配的是不同集合,line count 把这种语义差异直接暴露了出来。

大文件语料上的表现:

基准(模式)rg最慢者
subtitles_en_literal(Sherlock Holmes)0.123ag (lines) 1.868
subtitles_ru_literal(Шерлок Холмс)0.133ag (lines) 1.973
subtitles_en_surrounding_words\w+\s+Holmes\s+\w+0.200ugrep 43.220
subtitles_ru_no_literal(7 组\w{5}2.236ugrep 28.811

subtitles_en_surrounding_words中 ugrep 耗时 43.220s(±0.047),rg 仅 0.200s(±0.001),是该次运行中最极端的单项差距;俄语同型基准中 ugrep 亦为 42.919s。而subtitles_ru_alternate_casei则展示了另一面:ag (ASCII) 以 2.727s 拿下全场最快(rg 为 3.673s),但 rg 的 lines 为 735、ag 为 691——Unicode 忽略大小写使 rg 多匹配了部分西里尔词形,说明“最快”与“匹配最完整”未必是同一个命令,这正是 line count 校验存在的意义。类似地,linux_unicode_greek_casei中 ugrep 的 lines(105)与 rg(245)不一致,对照脚本中“Only ripgrep gets this right”的注释,可以推断 ugrep 在该用例上未能正确完成 Unicode 忽略大小写匹配。

六、如何复现这套基准

完整复现步骤(在 ripgrep 仓库根目录下):

  1. 安装 Python 3、git、GNU grep、ag、ugrep,以及待测的 rg(建议按本次运行的方式从源码构建):

    cargo build --release --features 'pcre2'
  2. 下载并准备语料(约 13GB,且需要编译整个 Linux 内核,耗时较长):

    ./benchsuite --dir /dev/shm/benchsuite --download all

    该命令是幂等的;也可以只准备部分语料,如--download linux

  3. 运行全部基准并落盘原始数据:

    ./benchsuite \ --dir /dev/shm/benchsuite \ --raw runs/<date>-<machine>/raw.csv \ | tee runs/<date>-<machine>/summary
  4. 常用变体:

    ./benchsuite --list # 列出全部基准项 ./benchsuite 'unicode' # 只运行名称匹配 unicode 的基准 ./benchsuite --disabled grep,ag # 跳过指定命令 ./benchsuite --warmup-iter 1 --bench-iter 5 # 提高采样精度

两点实践提示:其一,把--dir指向 tmpfs(如本次运行的/dev/shm/benchsuite)可显著降低 I/O 噪声,前提是内存装得下语料;其二,--raw已存在时会拒绝覆盖(--force可解除),避免误覆盖历史数据。若想对照历史结果,仓库中还保存了 2016、2018、2020 年的多组运行(如 benchsuite/runs/2016-12-24-archlinux-cheetah/summary),但注意 CPU 代际不同,跨机器只宜看量级不宜看绝对值。

七、如何正确看待这些数字

最后给出阅读这份(以及仓库中任何一次)benchsuite 运行记录时的适用前提与限制:

  • 单机结果:所有数据只在“i9-12900K + 128GB + Arch Linux + tmpfs”这一组合下成立,工具版本(rg 13.0.0、grep 3.8、ag 2.2.0、git 2.39.0、ugrep 3.9.2)也是数据的一部分,换机器或换版本都可能改变排序;
  • 语料依赖:linux 语料是“大量小文件 + 编译垃圾”,subtitles 语料是“少量大文件”,两类语料考察的策略完全不同(文件遍历/过滤 vs 单文件内存搜索),不能由一类结果外推另一类;
  • 正确性优先于速度:同一基准中 lines 不一致说明语义或匹配正确性存在差异(ASCII/Unicode 词边界、二进制识别、Unicode case-folding),此时“快”没有意义;
  • 公平性是有代价的linux_literal_default是故意不公平的对照实验;而“公平”版本也只能对齐各工具能力的交集,脚本注释多次承认这种对齐的局限(如给 ugrep 加-a的补偿)。

理解以上边界后,这份 2022-12-16 的运行记录就不仅是一组静态数字,而是一套可复现、可验证、可继续扩展(新增bench_*函数即可加入基准项)的搜索工具评测方法论的完整实例。

【免费下载链接】ripgrepripgrep recursively searches directories for a regex pattern while respecting your gitignore项目地址: https://gitcode.com/GitHub_Trending/ri/ripgrep

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询