【问题标题】:Can a vector be moved and modified without an extra allocation?可以在没有额外分配的情况下移动和修改向量吗?
【发布时间】:2018-01-26 23:30:15
【问题描述】:

考虑以下代码:

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?

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

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

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

【问题讨论】:

  • 虽然这是一个关于优化的有趣问题,但请注意,如果您只写for i in &amp;mut u { *i += 1; },您可以更清楚地实现您的意图并且不必担心此类优化。
  • @PavelStrakhov 如果我理解正确,您的代码大约是我的伪代码的 Rust 翻译,没有原始的“功能样式”。另请注意,在我的示例中,u 是不可变的。

标签: vector iterator rust compiler-optimization


【解决方案1】:

让我们看看the sourceinto_iter()的实现Vec&lt;T&gt;

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,
        }
    }
}

创建IntoIter 迭代器会产生几个额外的分配,但不会分配给向量的元素;相反,会注册向量的底层内存详细信息。 the codemap() 后面怎么样?

fn map<B, F>(self, f: F) -> Map<Self, F> where
    Self: Sized, F: FnMut(Self::Item) -> B,
{
    Map{iter: self, f: f}
}

这里也没有分配额外的向量。最后一块拼图是collect()

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

这里没有答案; from_iter() 中的 the implementationVec&lt;T&gt; 怎么样?

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())
    }
}

这开始看起来很神奇,但相关的SpecExtend code 可能会揭示我们正在寻找的东西:

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:不,不能移动在没有额外分配的情况下修改向量。

【讨论】:

  • 您的帖子没有争论任何编译器阶段是否可以优化那些TL;DR: no - 他问是否有分配。
  • @the8472 您已经对答案中的必要优化提出了很好的一般性论点;我只是展示了证明过程复杂性的必要步骤,这使得它实际上是不可能的(在这一点上)。在 TL;DR 我指的是问题的标题,但我同意 - 当我在 PC 上时,我会更清楚地说明。
  • 啊,不清楚您要回答问题的哪一部分
  • 谢谢。为来源 +1。
【解决方案2】:

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

是的,避免分配的必要优化对于任何编译器层来说都太高级了,因为它们必须考虑堆分配和支撑@的不安全代码块中发生的指针魔术987654324@ 执行。在 drop-handlers、map 内部的恐慌和类似的事情开始发挥作用的一般情况下更是如此。

specializing
collect() -&gt; Vec&lt;T&gt; 可以针对特定类型(例如 std::iter::Map&lt;std::vec::IntoIter&lt;T&gt;, _&gt;)在库级别进行优化,但这些优化必须根据具体情况应用已知是安全的。

如果我理解正确,您的代码大致是我的伪代码的 Rust 翻译,没有原始的“功能样式”。

对于函数式风格,我们计划将for_each 添加到迭代器中,因此如果您采用iter_mut,则可以在库或编译器方面使用更少的英雄来实现等价。

它实际上会更直接地反映在原地做某事的意图,而不是仅仅神奇地进行一些优化,然后在稍作修改后就会出人意料地崩溃。

【讨论】:

    猜你喜欢
    • 2011-07-01
    • 2015-07-03
    • 2020-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多