完整的数据流
命令行参数
↓ (parse.rs, defs.rs)
结构化配置 (HiArgs)
↓ (hiargs.rs)
执行组件构建
├── WalkBuilder (文件发现)
├── SearchWorker (内容搜索)
└── Printer (结果输出)
↓
并行执行
↓ (WalkParallel + 工作窃取队列)
文件发现流
↓ (ignore 规则过滤)
Haystack 流
↓ (Searcher 策略选择)
搜索结果
↓ (Printer 格式化)
用户输出
文件发现与文件搜索
单线程模式:
// 文件发现阶段
let unsorted = args.walk_builder()?.build()
.filter_map(|result| haystack_builder.build_from_result(result));
let haystacks = args.sort(unsorted);
// 搜索阶段
for haystack in haystacks {
let search_result = searcher.search(&haystack);
}
并行模式
args.walk_builder()?.build_parallel().run(|| {
Box::new(move |result| {
let haystack = haystack_builder.build_from_result(result);
let search_result = searcher.search(&haystack);
})
});
缓冲机制分析
单线程模式
- 完整缓冲:所有文件先发现完毕,存储在haystacks集合中
- 排序支持:支持 –sort选项,因为有完整的文件列表
- 内存使用:文件路径全部在内存中
并行模式
- 流式处理: 文件发现和搜索同时进行
- 无缓冲: 没发现一个文件立即搜索,不等待其他文件
- 工作窃取: 多个线程并行发现和搜索文件
搜索内部的缓冲机制
在Searcher
内部有多种缓冲策略:
pub struct Searcher {
line_buffer: RefCell<LineBuffer>,
multi_line_buffer: RefCell<Vec<u8>>,
decode_buffer: RefCell<<u8>>,
}
三种搜索策略
- 内存映射:零拷贝,直接访问文件内容
- 流式读取:固定大小缓冲区,逐行处理
- 全文缓冲:多行搜索时,整个文件加载到内存
关键设计决策
为什么这样设计
- 性能优化:并行模式避免了文件列表的内存开销
- 内存控制:流式处理支持任意大小的文件
- 用户体验:结果可以立即输出,不需要等待所有文件扫描完成
缓冲区大小控制
// LineBuffer 默认配置
capacity: 64 * (1 << 10), //64kb 行缓冲
总结
- 不是一发现一搜索:ripgrep有两种模式
- 单线程: 先发现所有文件 → 缓冲排序 → 逐个搜索
- 并行: 发现和搜索同时进行,无文件级缓冲
- 缓冲区存在于多个层次:
- 文件列表级:单线程模式有完整缓冲
- 文件内容级:每个文件内部有行缓冲(64KB)
- 输出级:并行模式有输出缓冲区
核心架构对比
单线程模式(有缓冲)
文件发现 → [文件列表缓冲] → 排序 → 逐个搜索 → 输出
- 支持
--sort
选项 - 内存使用:O(文件数量)
并行模式(流式处理)
文件发现 ⟷ 搜索 → [输出缓冲] → 输出
- 实时处理,低内存占用
- 工作窃取队列协调多线程
性能影响
优势:
- 并行模式:内存效率高,结果输出快
- 单线程模式:支持排序,适合小规模搜索
权衡:
- 并行模式牺牲了排序能力换取性能
- 单线程模式需要更多内存但输出有序