ripgrep的完整架构

完整的数据流

命令行参数
     (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>>,
}
三种搜索策略
  1. 内存映射:零拷贝,直接访问文件内容
  2. 流式读取:固定大小缓冲区,逐行处理
  3. 全文缓冲:多行搜索时,整个文件加载到内存
关键设计决策
为什么这样设计
  1. 性能优化:并行模式避免了文件列表的内存开销
  2. 内存控制:流式处理支持任意大小的文件
  3. 用户体验:结果可以立即输出,不需要等待所有文件扫描完成
缓冲区大小控制
// LineBuffer 默认配置
capacity: 64 * (1 << 10),  //64kb 行缓冲

总结

  1. 不是一发现一搜索:ripgrep有两种模式
    • 单线程: 先发现所有文件 → 缓冲排序 → 逐个搜索
    • 并行: 发现和搜索同时进行,无文件级缓冲
  2. 缓冲区存在于多个层次:
    • 文件列表级:单线程模式有完整缓冲
    • 文件内容级:每个文件内部有行缓冲(64KB)
    • 输出级:并行模式有输出缓冲区
核心架构对比
单线程模式(有缓冲)
文件发现  [文件列表缓冲]  排序  逐个搜索  输出
  • 支持 --sort 选项
  • 内存使用:O(文件数量)
并行模式(流式处理)
文件发现  搜索  [输出缓冲]  输出
  • 实时处理,低内存占用
  • 工作窃取队列协调多线程

性能影响

优势

  • 并行模式:内存效率高,结果输出快
  • 单线程模式:支持排序,适合小规模搜索

权衡

  • 并行模式牺牲了排序能力换取性能
  • 单线程模式需要更多内存但输出有序
使用 Hugo 构建
主题 StackJimmy 设计