【问题标题】:How to take range from Vec without allocations and produce new Vec in Rust?如何在没有分配的情况下从 Vec 获取范围并在 Rust 中生成新的 Vec?
【发布时间】:2021-07-23 20:02:50
【问题描述】:

我想从Vec 中提取一个范围,方法是使用它并返回一个仅包含所需数据的新 Vec。我希望在不需要复制数据或进行任何分配的情况下发生这种情况。在 C 语言中,我会根据需要移动指针、更改长度字段并释放内存。

目前我正在使用drain,但我不确定它的性能特征(它会复制数据吗?)或者它的惯用程度。例如,如果我想要前 4 个元素,我会这样做:

fn main() {
    let mut a = vec![1,2,3,4,5,6,7,8];

    // Only want to keep the first 4 elements
    let a: Vec<u32> = a.drain(..4).collect();
    
    println!("{:?}", a);
}

我已经看过from_raw_parts 方法,但如果可能的话,我想避免对指针等感到烦躁。

【问题讨论】:

  • 你可以查看drain的来源,看看它在做什么。提示:当您使用doc.rust-lang.org 时,方法右侧的src 文本是指向该方法实现的链接。我使用您在问题中提供的链接链接了drain 的来源。
  • 如果只想要前4个元素,可以使用vec.truncate(4)

标签: optimization memory rust


【解决方案1】:

通用解决方案需要复制数据

一般来说,Vec 包含以下内容

  1. 一个整数capacity
  2. 可以包含capacity 元素的内存区域(我将称之为缓冲区)和
  3. 一个整数size,与size &lt;= capacity

capacity 是缓冲区在不调整大小的情况下可以包含的元素数,size 是缓冲区确实当前包含的元素数。

保存在Vec 中的元素位于缓冲区的开头(因此,从索引0size - 1)。这意味着要创建一个包含旧 Vec 的子范围的新 Vec,在一般情况下,需要复制一些元素 – 如果子范围不是从第一个元素开始, 新Vec 的第一个元素将不同于旧Vec 的第一个元素,你不能只重用缓冲区而不复制。

对于drain(),这发生在the Drop implementation of Drain

                if self.0.tail_len > 0 {
                    unsafe {
                        let source_vec = self.0.vec.as_mut();
                        // memmove back untouched tail, update to new length
                        let start = source_vec.len();
                        let tail = self.0.tail_start;
                        if tail != start {
                            let src = source_vec.as_ptr().add(tail);
                            let dst = source_vec.as_mut_ptr().add(start);
                            ptr::copy(src, dst, self.0.tail_len);
                        }
                        source_vec.set_len(start + self.0.tail_len);
                    }
                }

如果你想从前面保留元素

如果您只想保留前面的元素并删除后面的元素,则有一种无需复制元素的解决方案。您可以使用truncate()。你可以look at its source,它不会复制数据(在我看来,这比在这种情况下使用drain() 更惯用)。

fn main() {
    let mut a = vec![1, 2, 3, 4, 5, 6, 7, 8];

    a.truncate(4);
    
    assert_eq!(a, vec![1, 2, 3, 4]);
}

Playground link

为了完整起见:这不会缩小向量的容量。如果您要截断很多元素,然后保留截断的 Vec 而不添加新元素,那么您就是在浪费内存。在这种情况下,在截断后调用shrink_to_fit() 可能是个好主意(尽管这可能复制了Vec 的内容,因此成本更高)。

drain() 在这种特殊情况下也不应该复制数据(self.0.tail_len == 0 在这种情况下,所以上面的代码 sn-p 不会被执行),但与使用 truncate 相比可能更不清楚直接。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多