使用 criterion 库 进行 benchmark#

刚开始学 python 时,很喜欢使用自带的 timeit 库测量自己写的代码的执行时间。

虽然学了不少语言,但是没怎么关注过其他语言 benchmark 库。

今天在学习使用 nom 时,突然想到对比一下两种不同实现的 csv 解析器的代码实现,于是有了这篇文章。

csv 解析器实现#

所用的 CSV 数据可以从 sample-csv-files 下载。

实现 1#

// csv.rs
pub fn parse_csv(input: &str) -> IResult<&str, Vec<Vec<&str>>> {
    separated_list0(line_ending, parse_line)(input)
}

fn parse_line(line: &str) -> IResult<&str, Vec<&str>> {
    separated_list1(char(','), parse_filed)(line)
}
//  解析 "Alice Blic" 这样的双引号的字段
fn parse_quoted_files(inut: &str) -> IResult<&str, &str> {
    delimited(char('"'), take_until("\""), char('"'))(inut)
}

// 解析正常字段
fn parse_normal_filed(input: &str) -> IResult<&str, &str> {
    recognize(is_not(",\r\n"))(input)
}

fn parse_filed(input: &str) -> IResult<&str, &str> {
    alt((parse_quoted_files, parse_normal_filed))(input)
}

实现 2#

// csv2.rs
pub fn parse_csv(input: &str) -> IResult<&str, Vec<Vec<&str>>> {
    // terminated => combinator
    // line_ending => parser
    // opt => combinator
    separated_list0(terminated(line_ending, opt(line_ending)), parse_line)(input)
}

// parse_line => parser
fn parse_line(input: &str) -> IResult<&str, Vec<&str>> {
    // separated_list0 => a combinator
    // accepts 2 parser
    separated_list0(char(','), is_not(",\r\n"))(input)
}

编写 benchmark#

引入 criterion

cargo add --dev criterion

Cargo.toml 中指定有关属性:

[[bench]]
name = "my_benchmark"
harness = false

项目根目录下创建 benches 目录,并创建文件 my_benchmark.rs

├─benches
│      my_benchmark.rs

编写 benchmark 函数

fn benchmark_both_parsers(c: &mut Criterion) {
    let content = fs::read_to_string("customers-100000.csv").unwrap();

    let mut group = c.benchmark_group("CSV Parser Comparison");

    group.bench_function("parse_csv", |b| {
        b.iter(|| {
            let result = parse_csv(black_box(&content));
            black_box(result);
        })
    });

    group.bench_function("parse_csv2", |b| {
        b.iter(|| {
            let result = parse_csv2(black_box(&content));
            black_box(result);
        })
    });

    group.finish();
}

criterion_group!(benches, benchmark_both_parsers);
criterion_main!(benches);

最后在命令行运行 cargo bench 即可。结果在命令行中也有输出,不过 criterion 默认生成的 HTML 报告信息量更大且美观。

打开 target/criterion/report/index.html

cargo bench criterion html report