围绕着你的例子,我很惊讶地看到
索引计算(y*width+x)似乎不是
由优化器简化。
此外,我虽然内存访问更重要
比索引计算中删除的乘法。
但是当谈到时间安排时,我可以看到很大的不同
(具有各种网格大小和真/假初始化)。
编辑
看了下面的一些cmets,对比了两个版本,
_v1 和 _v2,在编译器资源管理器 (godbolt) 中。
它们看起来非常相似,除了 _v1 使用 lea
指令和 _v2 使用 add 和 inc 代替。
根据vtune-amplifier,_v1的大部分时间是
花费在这个确切的lea 指令上。
所以,至少在我的电脑上,在这个具体的例子上,
与直觉相反,选择 lea 而不是 add 和
inc 似乎比不规则的内存访问更有害
模式。
/*
$ rustc -C opt-level=3 prog.rs && ./prog
avg1=50.5
v1: 4453 ms
avg2=50.5
v2: 2361 ms
*/
pub struct Board {
width: usize,
height: usize,
data: Vec<bool>,
}
impl Board {
pub fn new(
width: usize,
height: usize,
) -> Self {
Self {
width,
height,
data: vec![false; width * height],
}
}
pub fn calc_avg_height_v1(&self) -> f32 {
let mut heights = 0;
for x in 0..self.width {
for y in 0..self.height {
if self.data[(y * self.width) + x] {
heights += self.height - y;
break;
}
}
}
heights as f32 / self.width as f32
}
pub fn calc_avg_height_v2(&self) -> f32 {
let mut heights = 0;
for x in 0..self.width {
let mut idx = x;
for y in 0..self.height {
if self.data[idx] {
heights += self.height - y;
break;
}
idx += self.width;
}
}
heights as f32 / self.width as f32
}
}
pub fn run<F>(
name: &str,
repeat: usize,
fnct: F,
) -> u128
where
F: Fn() -> f32,
{
let mut prev = -1.0_f32;
let warmup = repeat / 10;
for _ in 0..warmup {
let avg = fnct();
if avg != prev {
println!("{}={}", name, avg);
prev = avg;
}
}
let now = std::time::Instant::now();
for _ in 0..repeat {
let avg = fnct();
if avg != prev {
println!("{}={}", name, avg);
prev = avg;
}
}
now.elapsed().as_millis()
}
pub fn main() {
let mut board = Board::new(100, 100);
for y in 0..board.height {
for x in 0..board.width {
board.data[y * board.width + x] = x == y;
}
}
let repeat = 1_000_000;
println!(
" v1: {} ms",
run("avg1", repeat, || board.calc_avg_height_v1())
);
println!(
" v2: {} ms",
run("avg2", repeat, || board.calc_avg_height_v2())
);
}