【发布时间】:2020-04-22 14:39:40
【问题描述】:
我编写了这个非常简单的 Rust 函数:
fn iterate(nums: &Box<[i32]>) -> i32 {
let mut total = 0;
let len = nums.len();
for i in 0..len {
if nums[i] > 0 {
total += nums[i];
} else {
total -= nums[i];
}
}
total
}
我编写了一个基本的基准测试,它使用有序数组和随机数组调用方法:
fn criterion_benchmark(c: &mut Criterion) {
const SIZE: i32 = 1024 * 1024;
let mut group = c.benchmark_group("Branch Prediction");
// setup benchmarking for an ordered array
let mut ordered_nums: Vec<i32> = vec![];
for i in 0..SIZE {
ordered_nums.push(i - SIZE/2);
}
let ordered_nums = ordered_nums.into_boxed_slice();
group.bench_function("ordered", |b| b.iter(|| iterate(&ordered_nums)));
// setup benchmarking for a shuffled array
let mut shuffled_nums: Vec<i32> = vec![];
for i in 0..SIZE {
shuffled_nums.push(i - SIZE/2);
}
let mut rng = thread_rng();
let mut shuffled_nums = shuffled_nums.into_boxed_slice();
shuffled_nums.shuffle(&mut rng);
group.bench_function("shuffled", |b| b.iter(|| iterate(&shuffled_nums)));
group.finish();
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
令我惊讶的是,这两个基准测试具有几乎完全相同的运行时间,而 Java 中的类似基准测试显示两者之间存在明显差异,这可能是由于在 shuffled 情况下分支预测失败。
我见过提到条件移动指令,但如果我 otool -tv 可执行文件(我在 Mac 上运行),我在 iterate 方法输出中看不到任何内容。
谁能解释为什么 Rust 中的有序和无序案例之间没有明显的性能差异?
【问题讨论】:
-
我怀疑这与 Rust/LLVM 如何将此类循环优化为 SIMD 指令有关(我相信 Java 无法做到这一点)。
-
@Frxstrem,是的,在我的计算机上它使用 AVX ISA,即使在 Rust Playground 中,它也会使用“如果小于条件移动”指令
cmovll来扁平化逻辑。 -
@sshashank124: 是的,启用了完全优化 (
-O3) 现代提前编译器后端(如 LLVM 和 GCC)通常会执行“if-conversion”分支到 CMOV 或其他无分支序列。这也是自动矢量化的先决条件。
标签: performance rust compiler-optimization branch-prediction llvm-codegen