有时我有多个相同类型的标量变量,并且想要迭代它们。
为简单起见,我们假设类型为
usize
,变量数量为 1000
,这意味着它们的数组适合堆栈。
此后,我们将术语“快”定义为最坏情况的时间复杂度;如果一种方法“可能”非常快,那么我们并不是说它很快,因为编译器优化并不总是保证会发生。 方法1
):
use rand::prelude::*;
fn main() {
let mut rng = StdRng::seed_from_u64(0);
loop {
let a1 = rng.gen::<usize>();
let a2 = rng.gen::<usize>();
let a3 = rng.gen::<usize>();
/* ... */
let a1000 = rng.gen::<usize>();
for e in vec![a1, a2, a3, /* ... */ a1000] {
println!("{}", e);
}
}
}
在此示例中,我们有许多标量变量
a1
、
a2
、...、a1000
,为了迭代它们,我们将它们存储在 Vec<usize>
中。显然这种方法效率低下,因为它分配堆内存并将所有变量复制到其中。
方法2
[usize; 1000]
类型的数组,我认为数组本身是在堆栈中分配的。但是,我认为变量仍然被复制到其中。
for e in [a1, a2, a3, /* ... */ a1000] {
println!("{}", e);
}
方法3
[&usize; 1000]
类型的数组,现在变量不会被复制。然而,我认为这种方法在内部创建了许多
pointer,我认为这与复制整数一样慢(将整数作为常量引用传递与复制 - Stack Overflow)。
for e in [&a1, &a2, &a3, /* ... */ &a1000] {
println!("{}", e);
}
我想最小化堆分配和复制。有什么好的办法吗?我们有这种幼稚的方法,但可读性和可维护性太低(并且由于二进制大小的增加,其最坏情况的时间复杂度可能不好):
println!("{}", a1);
println!("{}", a2);
/* ... */
println!("{}", a1000);
这是我目前最好的方法:
use rand::prelude::*;
macro_rules! for_each {
($f:expr, $($var:ident),*) => {
$(
$f($var);
)*
};
}
fn f(x: usize) {
println!("{}", x);
}
fn main() {
let mut rng = StdRng::seed_from_u64(0);
{
let a1 = rng.gen::<usize>();
let a2 = rng.gen::<usize>();
let a3 = rng.gen::<usize>();
/* ... */
let a1000 = rng.gen::<usize>();
for_each!(f, a1, a2, a3, /* ... */ a1000);
}
}
for
循环确实会很慢,但并不是你想象的那样:它会很慢,因为 LLVM 无法优化整个数组
的副本来创建迭代器。元素本身将被优化以几乎总是就地生成。这不能保证,但 codegen 的没有任何是可以保证的。理论上,编译器可能会在一天早上醒来并决定完全停止优化代码,并且按照标准(无论如何 Rust 中不存在该标准)就没问题。
实际上,编译器不会这样做,就像他们不会这样做一样,我们知道
他们会执行一些优化。人们在性能关键型程序中始终依赖这种直觉。大多数情况下它都有效(但如果您不确定的话,检查代码生成器总是好的,并且始终进行基准测试)。
如果你想更确定,你可以直接在数组中创建元素(没有中间变量) - for e in [rng.gen(), rng.gen(), ...]
usize
这样的类型并不重要。
要解决 for 循环的问题,您可以迭代切片:
let arr = [a1, a2, a3, /* ... */ a1000];
for &e in &arr {
println!("{}", e);
}