rust std::iter::一次的费用是多少?

omqzjyyz  于 2022-11-30  发布在  其他
关注(0)|答案(2)|浏览(115)

如果我有this这样的代码:

pub fn f(x: u16) -> impl Iterator<Item = u16> {
    std::iter::once(0).chain((x >> 10)..x)
}

如果我使用once将一个常量链接到一个迭代器前面,那么当这个迭代器被消耗时,我是否要为此付出O(n)的代价呢?我无法从汇编中判断出来(除了它明确地在内存中建立了一个额外的数据结构之外)。

qltillow

qltillow1#

如果我在迭代器前面使用一次来链接一个常量,我是否要为此付出O(n)的代价?
O(n)什么n?
在我的头脑中,一旦在运行时转换为if,这将表明它是。
代码方面的Once实际上就是Option迭代器的一个 Package 器,that 做的是在它自己上调用take
take为:

mem::replace(self, None)

所以once中没有实际的if at runtime,条件在迭代协议中。
chain中有一些条件,因为 it 需要从第一个操作符切换到第二个操作符,所以在那里它变得有点复杂。
我怎么知道?
可能更容易,如果你使用godbolt,因为它有一个更好的装配视图比操场。

kknvjkwl

kknvjkwl2#

如果你想知道once()的时间复杂度是O(n),那么你需要考虑的是chain()的时间复杂度,而不是once()的时间复杂度,因为至少在优化之前,<Chain as Iterator>::next()必须在每一步决定是从第一个迭代器还是第二个迭代器中取一个元素。
然而,通过使用 internal iteration -迭代器的控制流是迭代器本身的责任,这样就不需要重复调用next(),可以避免招致这种开销。最像for的方式是for_each()。如果查看由这段代码生成的程序集,您可以看到在main2()中使用for_each()的版本要简单得多:

pub fn f(x: u16) -> impl Iterator<Item = u16> {
    std::iter::once(0).chain((x >> 10)..x)
}

pub fn main1() -> u16 {
    let mut sum = 0;
    for item in f(3) {
        sum += std::hint::black_box(item);
    }
    sum
}

pub fn main2() -> u16 {    
    let mut sum = 0;
    f(3).for_each(|item| {
        sum += std::hint::black_box(item);
    });
    sum
}

black_box(),我过去一直不鼓励将循环优化为常量,预计在Rust 1.66.0中是稳定的--目前,使用nightly构建。)
这样做的原因是for_each()是根据fold()实现的,Chain迭代器覆盖了fold()的默认实现,这样它就调用了第一个迭代器自己的fold(),然后是第二个迭代器自己的fold()
你不需要显式地使用for_each()fold()--你应该期待任何可以 * 从中受益的迭代器操作,比如sum()collect(),都会这样做。一般来说,如果你正在寻找最佳的迭代器性能,并且涉及到像chain()这样的东西,**尝试使用对迭代器的单个调用来表达你的操作,**而不是next()或转换为next()的内容,例如for item in iter {}循环。
但是,如果要使用continueawait之类的控制流构造,则需要一个for循环。

相关问题