可以移动和修改向量而无需额外分配吗?

时间:2017-08-18 10:01:48

标签: vector iterator rust compiler-optimization

请考虑以下代码:

let u: Vec<u8> = (64..74).collect();
let v: Vec<u8> = u.iter().map(|i| i + 1).collect();

u未被移动,因此v不可避免地被重新分配。

但如果我这样做:

let w: Vec<u8> = u.into_iter().map(|i| i + 1).collect();

u已被移动,w是其转化的名称。这是一些代表我的意思的伪代码:

mark u as "moved"
for i = 0..10:
    u[i] += 1
w = u

(我认为)不需要新的分配,因为我们将类型映射到自身。对于此代码,情况并非如此:

let t: Vec<u8> = (64..74).collect();
let s: String = t.into_iter().map(|i| i as char).collect();

总结一下我的问题

当我们将Vec转换为迭代器然后将此迭代器映射到相同类型的元素上的迭代器时,是否分配了新的Vec然后将结果收集到Vec

如果确实有分配,为什么?

我尝试--emit=mir,但我无法找到答案。我每晚使用rustc 1.20(如果重要的话)。

If you want to play with code: Try it online!

2 个答案:

答案 0 :(得分:4)

让我们into_iter()了解Vec<T> fn into_iter(mut self) -> IntoIter<T> { unsafe { let begin = self.as_mut_ptr(); assume(!begin.is_null()); let end = if mem::size_of::<T>() == 0 { arith_offset(begin as *const i8, self.len() as isize) as *const T } else { begin.offset(self.len() as isize) as *const T }; let cap = self.buf.cap(); mem::forget(self); IntoIter { buf: Shared::new(begin), cap: cap, ptr: begin, end: end, } } } 的实施情况{/ 3}}:

IntoIter

创建map()迭代器会产生几个额外的分配,但不会导致向量的元素;相反,向量的基础内存细节被注册。 fn map<B, F>(self, f: F) -> Map<Self, F> where Self: Sized, F: FnMut(Self::Item) -> B, { Map{iter: self, f: f} } 后面的the source怎么样?

fn collect<B: FromIterator<Self::Item>>(self) -> B where Self: Sized {
    FromIterator::from_iter(self)
}

此处也没有分配额外的载体。最后一块拼图是the code

from_iter()

这里没有答案; Vec<T>的{​​{1}} collect()怎么样?

impl<T> FromIterator<T> for Vec<T> {
    #[inline]
    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Vec<T> {
        <Self as SpecExtend<T, I::IntoIter>>::from_iter(iter.into_iter())
    }
}

这开始看起来像魔术,但也许相关的the implementation将揭示我们正在寻找的东西:

impl<T, I> SpecExtend<T, I> for Vec<T>
    where I: Iterator<Item=T>,
{
    default fn from_iter(mut iterator: I) -> Self {
        // Unroll the first iteration, as the vector is going to be
        // expanded on this iteration in every case when the iterable is not
        // empty, but the loop in extend_desugared() is not going to see the
        // vector being full in the few subsequent loop iterations.
        // So we get better branch prediction.
        let mut vector = match iterator.next() {
            None => return Vec::new(),
            Some(element) => {
                let (lower, _) = iterator.size_hint();
                let mut vector = Vec::with_capacity(lower.saturating_add(1));
                unsafe {
                    ptr::write(vector.get_unchecked_mut(0), element);
                    vector.set_len(1);
                }
                vector
            }
        };
        <Vec<T> as SpecExtend<T, I>>::spec_extend(&mut vector, iterator);
        vector
    }

    default fn spec_extend(&mut self, iter: I) {
        self.extend_desugared(iter)
    }
}

在此代码中,我们最终可以看到调用的Vec::newVec::with_capacity方法为结果向量分配新空间。

TL; DR :不,无法在没有额外分配的情况下移动修改矢量。

答案 1 :(得分:2)

  

当我们将Vec转换为迭代器然后将此迭代器映射到相同类型的元素上的迭代器然后将结果收集到Vec中时,是否存在新Vec的分配?

是的,避免分配的必要优化对于任何编译器层来说都是太高级别,因为他们必须推断堆分配以及支撑{的不安全代码块中的指针魔术。 {1}}实施。在一般情况下,Vec内部出现恐慌,类似的事情会发挥作用。

可以通过specializing来在图书馆级别进行优化 map用于特定类型,例如collect() -> Vec<T>,但这些优化必须根据具体情况应用,以确保安全。

  

如果我理解正确,你的代码大致是我的伪代码的Rust翻译,没有原始的&#34;功能样式&#34;。

对于函数样式,有一个计划将for_each添加到迭代器中,因此如果你使用iter_mut,可以通过库或编译器端的英雄少得多来实现等效的东西。

它实际上反映了更直接地做某事的意图,而不仅仅是神奇地进行了一些优化,然后可能会在轻微修改下出人意料地突破。