【问题标题】:Is Vec::splice() efficient when the length doesn't change?当长度不变时, Vec::splice() 是否有效?
【发布时间】:2023-03-29 00:58:02
【问题描述】:

当您使用Vec::splice() 替换Vec 的一部分时,是否足够聪明地专门处理替换与它正在替换的块长度相同的情况,还是应该我自己处理?

这样做有什么意义吗,或者array.splice() 足够聪明,无论如何都可以这样做?

fn example(array: Vec<u64>, replacement: Vec<u64>, range: std::ops::Range<usize>) {
    if (replacement.len() == range.len()) {
        for i in range {
            array[i] = replacement[i];
        }
    } else {
        array.splice(range, replacement);
    }
}

【问题讨论】:

标签: rust vec


【解决方案1】:

我放弃并阅读了代码。 足够聪明,可以处理这种情况。以下是代码的工作原理:

  1. 您调用.splice(),它会创建一个Splice 对象,其中包含一个用于替换范围的Drain 迭代器:

    pub fn splice<R, I>(&mut self, range: R, replace_with: I) -> Splice<'_, I::IntoIter>
    where
        R: RangeBounds<usize>,
        I: IntoIterator<Item = T>,
    {
        Splice { drain: self.drain(range), replace_with: replace_with.into_iter() }
    }
    
  2. Splice 被删除,它调用了它的 drop() 实现。

    impl<I: Iterator> Drop for Splice<'_, I> {
        fn drop(&mut self) {
    

    它做的第一件事就是删除所有被替换的元素。

            self.drain.by_ref().for_each(drop);
    

    然后它检查替换是否在Vec 的末尾,如果是,则将其扩展并返回。

            unsafe {
                if self.drain.tail_len == 0 {
                    self.drain.vec.as_mut().extend(self.replace_with.by_ref());
                    return;
                }
    

    接下来,它调用一个帮助器 fill() 函数,用替换迭代器 (replace_with) 中的尽可能多的元素替换已删除的元素。 fill() 返回 false 如果它没有填满整个替换范围,在这种情况下它会返回(虽然我不确定在这种情况下尾部移动到哪里?)

                // First fill the range left by drain().
                if !self.drain.fill(&mut self.replace_with) {
                    return;
                }
    

    现在replace_with 可能剩下 0 个元素(在这种情况下我们完成了),或者它可能有更多元素(在这种情况下,尾部需要向后移动该数量)。这就是接下来发生的事情。

                // There may be more elements. Use the lower bound as an estimate.
                // FIXME: Is the upper bound a better guess? Or something else?
                let (lower_bound, _upper_bound) = self.replace_with.size_hint();
                if lower_bound > 0 {
                    self.drain.move_tail(lower_bound);
                    if !self.drain.fill(&mut self.replace_with) {
                        return;
                    }
                }
    

    您可能期望if lower_bound == 0 { return; },但lower_bound 只是一个估计值,所以它首先尝试,如果失败,它将替换复制到一个临时向量中,因此它可以知道完整长度。

                // Collect any remaining elements.
                // This is a zero-length vector which does not allocate if `lower_bound` was exact.
                let mut collected = self.replace_with.by_ref().collect::<Vec<I::Item>>().into_iter();
                // Now we have an exact count.
                if collected.len() > 0 {
                    self.drain.move_tail(collected.len());
                    let filled = self.drain.fill(&mut collected);
                    debug_assert!(filled);
                    debug_assert_eq!(collected.len(), 0);
                }
    

    关键是如果replace_with 为空,因为替换大小与范围相同,那么所有这些操作都是相当简单的nop-ish。

    最后一位丢弃 Drain 迭代器本身,这将移动尾部..可能再次?我不知道我在这里迷路了。反正长度一样肯定不会动的。

            }
            // Let `Drain::drop` move the tail back if necessary and restore `vec.len`.
        }
    }
    

【讨论】:

    【解决方案2】:

    根据the docsanother answersplice 在少数情况下是“智能”(最佳),包括“[replace_with] 的size_hint() 的下限是精确的”。

    在您的示例函数中,replacement.iter() 上的size_hint() 的下限是精确的,因此应该执行一次内存移动。

    【讨论】:

      【解决方案3】:

      据我所知,Splice::drop 是大部分工作,它调用fill,在匹配长度的情况下,它将遍历replace_with,并返回true,但是将replace_with 留空,因此lower_bound 为0,collected.len() 为0。

      Drain::drop 然后运行。循环应该什么都不做,因为Splice::drop 已经对其进行了迭代,然后DropGuard::drop 可能需要移动元素,不需要移动/复制元素,因为由于长度匹配,@987654333 @ 应该等于 start

      【讨论】:

        猜你喜欢
        • 2020-01-23
        • 2010-10-27
        • 1970-01-01
        • 2013-01-19
        • 1970-01-01
        • 1970-01-01
        • 2020-09-25
        • 2018-04-26
        • 2019-07-05
        相关资源
        最近更新 更多