Rust性能测试库tango-bench的使用,tango-bench提供高效精准的基准测试和性能分析工具

Rust性能测试库tango-bench的使用,tango-bench提供高效精准的基准测试和性能分析工具

Tango.rs是一个新颖的基准测试框架,采用配对基准测试(paired benchmarking)来评估代码性能。这种方法利用了同时执行的两个函数比连续执行的两个函数更高效测量性能差异的事实。

特性:

  • 对变化非常高的敏感性,比传统(点式)方法更快地收敛结果,通常只需几分之一秒
  • 能够比较来自不同VCS提交的同一代码的不同版本(A/B基准测试)
  • 使用tokio.rs的异步支持
  • 支持macOS、Linux和Windows

1秒、1%、1误差

与传统点式基准测试相比,配对基准测试对变化明显更敏感。这种更高的敏感性能够早期检测到统计上显著的性能变化。

Tango设计为能够在至少9次测试运行中,在短短1秒内检测到1%的性能变化。

准备工作

  1. 安装Rust和Cargo工具链(在Linux/macOS上支持Rust稳定版,Windows需要nightly版本)
  2. (可选)安装cargo-export

快速开始

  1. 添加cargo依赖并创建新基准测试:
[dev-dependencies]
tango-bench = "0.5"

[[bench]]
name = "factorial"
harness = false
  1. 允许rustc从基准测试中导出符号进行动态链接

    • (Linux/macOS) 添加构建脚本(build.rs)包含以下内容
    fn main() {
        println!("cargo:rustc-link-arg-benches=-rdynamic");
        println!("cargo:rerun-if-changed=build.rs");
    }
    
    • (Windows, 需要nightly) 在cargo配置(.cargo/config)中添加以下代码
    [build]
    rustflags = ["-Zexport-executable-symbols"]
    
  2. 添加benches/factorial.rs包含以下内容:

use std::hint::black_box;
use tango_bench::{benchmark_fn, tango_benchmarks, tango_main, IntoBenchmarks};

// 计算阶乘的函数
pub fn factorial(mut n: usize) -> usize {
    let mut result = 1usize;
    while n > 0 {
        result = result.wrapping_mul(black_box(n));
        n -= 1;
    }
    result
}

// 定义基准测试
fn factorial_benchmarks() -> impl IntoBenchmarks {
    [
        benchmark_fn("factorial", |b| b.iter(|| factorial(500))),
    ]
}

tango_benchmarks!(factorial_benchmarks());
tango_main!();
  1. 构建并将基准测试导出到target/benchmarks目录:
$ cargo export target/benchmarks -- bench --bench=factorial
  1. 修改factorial.rs使阶乘更快:
fn factorial_benchmarks() -> impl IntoBenchmarks {
    [
        benchmark_fn("factorial", |b| b.iter(|| factorial(495))),
    ]
}
  1. 比较新版本与已构建的版本:
$ cargo bench -q --bench=factorial -- compare target/benchmarks/factorial
factorial             [ 375.5 ns ... 369.0 ns ]      -1.58%*

结果显示factorial(500)factorial(495)之间确实存在约1%的差异。

异步支持

要在异步设置中使用Tango.rs,请按照以下步骤操作:

  1. Cargo.toml中添加tokiotango-bench依赖:
[dev-dependencies]
tango-bench = { version = "0.5", features = ["async-tokio"] }

[[bench]]
name = "async_factorial"
harness = false
  1. 创建benches/async_factorial.rs包含以下内容:
use std::hoint::black_box;
use tango_bench::{
    async_benchmark_fn, asynchronous::tokio::TokioRuntime, tango_benchmarks, tango_main,
    IntoBenchmarks,
};

// 异步阶乘函数
pub async fn factorial(mut n: usize) -> usize {
    let mut result = 1usize;
    while n > 0 {
        result = result.wrapping_mul(black_box(n));
        n -= 1;
    }
    result
}

// 定义异步基准测试
fn benchmarks() -> impl IntoBenchmarks {
    [async_benchmark_fn("async_factorial", TokioRuntime, |b| {
        b.iter(|| async { factorial(500).await })
    })]
}

tango_benchmarks!(benchmarks());
tango_main!();
  1. 像同步情况一样构建和使用基准测试:
$ cargo bench -q --bench=async_factorial -- compare

运行器参数

可以传递给compare命令的几个参数来改变其行为:

  • -t, --time - 每个基准测试运行多长时间(秒)
  • -s, --samples - 从每个基准测试收集多少样本
  • -f - 按名称过滤基准测试,支持glob模式
  • -d [path] - 在给定目录中dump原始样本的CSV
  • --gnuplot - 为每个基准测试生成绘图(需要安装gnnuplot)
  • -o, --filter-outliers - 额外过滤异常值
  • -p, --parallel - 在两个不同线程中运行基础/候选函数,而不是在单个线程中交错
  • --fail-threshold - 如果新版本比基线慢给定百分比则失败
  • --fail-fast - 在第一个超过失败阈值的基准测试后失败,而不是在整个套件后

贡献

项目处于早期阶段,任何帮助都将受到赞赏。以下是一些可能感兴趣的想法:

  • 找到一种方法为系统中的注册函数提供更用户友好的API
  • 如果你是库作者,试用tango并提供反馈将非常有用

完整示例demo

下面是一个完整的字符串处理基准测试示例:

  1. 首先创建Cargo.toml配置:
[dev-dependencies]
tango-bench = "0.5"

[[bench]]
name = "string_ops"
harness = false
  1. 添加构建脚本build.rs
fn main() {
    println!("cargo:rustc-link-arg-benches=-rdynamic");
    println!("cargo:rerun-if-changed=build.rs");
}
  1. 创建字符串操作基准测试文件benches/string_ops.rs:
use std::hint::black_box;
use tango_bench::{benchmark_fn, tango_benchmarks, tango_main, IntoBenchmarks};

// 字符串拼接函数
fn concat_strings(a: &str, b: &str) -> String {
    format!("{}{}", a, b)
}

// 字符串反转函数
fn reverse_string(s: &str) -> String {
    s.chars().rev().collect()
}

// 定义基准测试套件
fn string_benchmarks() -> impl IntoBenchmarks {
    [
        benchmark_fn("concat_strings", |b| {
            b.iter(|| concat_strings(black_box("hello"), black_box("world")))
        }),
        benchmark_fn("reverse_string", |b| {
            b.iter(|| reverse_string(black_box("abcdefghijklmnopqrstuvwxyz")))
        }),
    ]
}

tango_benchmarks!(string_benchmarks());
tango_main!();
  1. 运行基准测试并比较结果:
# 首次运行并导出基准结果
$ cargo export target/benchmarks -- bench --bench=string_ops

# 修改代码后进行比较
$ cargo bench -q --bench=string_ops -- compare target/benchmarks/string_ops

这个完整示例展示了如何测试两个不同的字符串操作函数,并可以使用tango-bench来比较性能变化。


1 回复

Rust性能测试库tango-bench使用指南

tango-bench是一个高效的Rust基准测试和性能分析库,它提供了精确的测量工具来评估代码性能。

安装方法

Cargo.toml中添加依赖:

[dependencies]
tango-bench = "0.3"

基本使用方法

简单基准测试

use tango_bench::{benchmark_fn, Benchmark};

// 斐波那契数列计算函数
fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 1,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

fn main() {
    // 创建基准测试实例
    let mut benchmark = Benchmark::default();
    
    // 添加测试函数
    benchmark.add_function("fib 20", || fibonacci(20));
    benchmark.add_function("fib 30", || fibonacci(30));
    
    // 运行基准测试
    benchmark.run();
}

带参数的基准测试

use tango_bench::{benchmark_fn, Benchmark};

// 数据处理函数
fn process_data(data: Vec<u32>) -> u32 {
    data.iter().sum()
}

fn main() {
    let mut benchmark = Benchmark::default();
    
    // 测试不同大小的数据
    for size in [100, 1000, 10000] {
        let data = vec![1; size];
        benchmark.add_function(
            format!("process {} items", size),
            move || process_data(data.clone())
        );
    }
    
    benchmark.run();
}

高级功能

设置迭代次数

use tango_bench::{Benchmark, BenchmarkConfig};

fn main() {
    // 自定义基准测试配置
    let config = BenchmarkConfig {
        warmup_iterations: 10,   // 预热迭代次数
        measurement_iterations: 100,  // 测量迭代次数
        ..Default::default()
    };
    
    let mut benchmark = Benchmark::with_config(config);
    benchmark.add_function("fast operation", || {
        // 快速操作示例
    });
    
    benchmark.run();
}

比较两个实现

use tango_bench::{benchmark_fn, Benchmark};

// 旧算法实现
fn old_algorithm(input: &[u8]) -> usize {
    input.len()
}

// 新算法实现
fn new_algorithm(input: &[u8]) -> usize {
    input.iter().fold(0, |acc, _| acc + 1)
}

fn main() {
    let data = vec![0u8; 1024];
    
    let mut benchmark = Benchmark::default();
    benchmark.add_function("old algorithm", || old_algorithm(&data));
    benchmark.add_function("new algorithm", || new_algorithm(&data));
    
    benchmark.run();
}

输出解释

运行基准测试后,tango-bench会输出类似以下结果:

fib 20: 1.234 ms/iter (± 0.045 ms)
fib 30: 145.678 ms/iter (± 3.456 ms)
  • 第一列是测试名称
  • 第二列是每次迭代的平均时间
  • (± …) 表示测量结果的波动范围

最佳实践

  1. 确保测试函数足够大,单次执行时间至少几毫秒
  2. 避免在测试函数中使用println!等I/O操作
  3. 对于可变状态,使用move闭包或确保每次迭代重置状态
  4. 考虑使用black_box防止编译器过度优化

完整示例

下面是一个完整的基准测试示例,展示了如何使用tango-bench来比较不同字符串连接方法的性能:

use tango_bench::{Benchmark, BenchmarkConfig};

fn main() {
    // 配置基准测试参数
    let config = BenchmarkConfig {
        warmup_iterations: 5,
        measurement_iterations: 50,
        ..Default::default()
    };

    let mut benchmark = Benchmark::with_config(config);
    
    // 测试用数据
    let words = vec!["hello", "world", "rust", "performance", "testing"];
    
    // 测试+运算符连接字符串
    benchmark.add_function("+ operator", || {
        let mut result = String::new();
        for word in &words {
            result = result + word;
        }
        result
    });
    
    // 测试push_str方法连接字符串
    benchmark.add_function("push_str", || {
        let mut result = String::new();
        for word in &words {
            result.push_str(word);
        }
        result
    });
    
    // 测试join方法连接字符串
    benchmark.add_function("join", || {
        words.join("")
    });
    
    // 运行基准测试
    benchmark.run();
}

这个完整示例展示了如何:

  1. 配置基准测试参数
  2. 准备测试数据
  3. 测试不同的字符串连接方法
  4. 比较它们的性能

tango-bench提供了简单而强大的工具来测量和比较Rust代码的性能,是优化工作流程中的重要工具。

回到顶部