【问题标题】:How to have a reusable Vec<RwLockReadGuard> in a struct to avoid allocation?如何在结构中使用可重用的 Vec<RwLockReadGuard> 以避免分配?
【发布时间】:2020-06-11 18:05:09
【问题描述】:

我的算法使用Vec&lt;RwLockReadGuard&lt;..&gt;&gt; 来处理数据。该算法被重复调用,我不想每次调用它时都分配Vec,如果我可以在处理结束时只使用clear(),我会很高兴,并在下一次重用它将其存储在与数据处理相关的同一结构中。但是,RwLockReadGuard 的生命周期比持有结构的可能生命周期短。由于我只在数据处理函数内部使用Vec,而在它外部它总是空的,我还能以某种方式将它存储在结构中吗?是否有一个板条箱或成语可以帮助我解决这个问题?

显示问题的最小可重现示例位于问题的底部。

如果我每次都分配Vec,这就是它的样子:

#[derive(Clone)]
pub struct T;

pub fn process_ts(ts: &[T]) {
    unimplemented!();
}

struct Process {
    locked_ts: Vec<RwLock<Vec<T>>>,
}

impl Process {
    pub fn process(&self) {
        let mut ts: Vec<T> = Vec::with_capacity(self.locked_ts.len());

        let guards: Vec<RwLockReadGuard<Vec<T>>> = self
            .locked_ts
            .iter()
            .map(|locked_t| locked_t.read().unwrap())
            .collect();

        let n = guards.iter().map(|guard| guard.len()).min().unwrap();

        for i in 0..n {
            ts.clear();
            for t in &guards {
                ts.push(t[i].clone());
                process_ts(&ts);
            }
        }
    }
}

我不喜欢这个解决方案的是,每次调用Process::process 时,都会分配ts: Vec&lt;T&gt;guards: Vec&lt;RwLockReadGuard&lt;Vec&lt;T&gt;&gt;&gt;。我可以摆脱ts

struct ProcessReuseTs {
    locked_ts: Vec<RwLock<Vec<T>>>,
    reusable_ts: Vec<T>,
}

impl ProcessReuseTs {
    pub fn process(&mut self) {
        let guards: Vec<RwLockReadGuard<Vec<T>>> = self
            .locked_ts
            .iter()
            .map(|locked_t| locked_t.read().unwrap())
            .collect();

        let n = guards.iter().map(|guard| guard.len()).min().unwrap();

        for i in 0..n {
            self.reusable_ts.clear();
            for t in &guards {
                self.reusable_ts.push(t[i].clone());
                process_ts(&self.reusable_ts);
            }
        }
    }
}

但是如何提取guards

use std::sync::{RwLock, RwLockReadGuard};

#[derive(Clone)]
pub struct T;

pub fn process_ts(ts: &[T]) {
    unimplemented!();
}

struct ProcessReuseBoth {
    locked_ts: Vec<RwLock<Vec<T>>>,
    reusable_ts: Vec<T>,
    reusable_guards: Vec<RwLockReadGuard<Vec<T>>>,
}

impl ProcessReuseBoth {
    pub fn process(&mut self) {
        self.reusable_guards.clear();
        self.reusable_guards.extend(
            self.locked_ts
                .iter()
                .map(|locked_t| locked_t.read().unwrap()),
        );

        let n = self
            .reusable_guards
            .iter()
            .map(|guard| guard.len())
            .min()
            .unwrap();

        for i in 0..n {
            self.reusable_ts.clear();
            for t in &self.reusable_guards {
                self.reusable_ts.push(t[i].clone());
                process_ts(&self.reusable_ts);
            }
        }

        self.reusable_guards.clear();
    }
}

pub fn main() {
    unimplemented!()
}

不能用

编译
error[E0106]: missing lifetime specifier
  --> src/main.rs:13:26
   |
13 |     reusable_guards: Vec<RwLockReadGuard<Vec<T>>>,
   |        

Playground

【问题讨论】:

  • edit您的问题并粘贴您收到的确切和全部错误 - 这将帮助我们了解问题所在,以便我们提供最佳帮助。有时试图解释错误消息很棘手,实际上错误消息的不同部分很重要。请使用直接运行编译器的消息,而不是 IDE 生成的消息,它可能会尝试为您解释错误。
  • 很难回答您的问题,因为它不包含minimal reproducible example。我们无法分辨代码中存在哪些 crate(及其版本)、类型、特征、字段等。如果您尝试在Rust Playground 上重现您的错误,如果可能的话,这将使我们更容易为您提供帮助,否则在一个全新的 Cargo 项目中,然后在edit 您的问题中包含附加信息。您可以使用Rust-specific MRE tips 来减少您在此处发布的原始代码。谢谢!
  • 看来Why can't I store a value and a reference to that value in the same struct? 的答案可能会回答您的问题。如果没有,请edit您的问题来解释差异。否则,我们可以将此问题标记为已回答。
  • 我添加了最小可重现示例和确切错误。您建议的答案非常广泛,涵盖了很多主题,我看不出它如何解决我的问题(也许可以,但我看不出如何应用它)。我想说不同之处在于,在我的情况下,引用的 Vec 在 fn 之外始终为空,并且具有对结构的独占(&mut)访问。它基本上仅用作 fn 内部临时使用的预分配区域。难道没有一些巧妙的方法可以利用这个属性吗?

标签: rust lifetime


【解决方案1】:

归结为,您似乎要做的是分配一个Vec。使用它来存储RwLockReadGuard&lt;'a, Vec&lt;T&gt;&gt; 类型的元素一段时间'a,然后清除向量并将其放入RwLockReadGuard&lt;'b, Vec&lt;T&gt;&gt; 类型的元素,其中生命周期'b 是与'a 不同的生命周期(实际上没有与它重叠),依此类推。这是行不通的,因为RwLockReadGuard&lt;'a, Vec&lt;T&gt;&gt;RwLockReadGuard&lt;'b, Vec&lt;T&gt;&gt; 是不同的类型,我们无法更改Vec 所持有的元素类型。

但也许真正的目标不是用相同的Vec 保存这些不同类型的元素(这是不可能的),而是避免需要重新分配每个新的Vec。我们可能会问,是否有可能从旧的Vec 回收分配的内存以跳过必须分配下一个Vec?好吧,对于一些非常丑陋、不安全的代码,可能只分配一个Vec&lt;u8&gt;,然后在每次调用process 时进行一些指针争论,将其就地转换为所需类型的Vec(大小为零但容量非零);这可能很难正确完成,并且需要取决于stdVec 实现的内部细节。

也许值得退后一步,并认识到每次我们在堆上分配某些东西时都会问同样的问题——也就是说,有没有办法重用我们刚刚释放的空间以避免不得不做新的配置?在某些情况下,答案可能是肯定的,但是我们不得不问,为了进行优化而弄乱我们的代码是否值得?

这就引出了一个问题——我们是否有任何证据表明这里的分配实际上是一个重要的性能瓶颈?如果没有,也许没有必要担心。如果您确实需要提高分配的性能,可以尝试使用jemalloc 或某种竞技场。

【讨论】:

  • 是的,“是否可以从旧的 Vec 回收分配的内存,以避免分配下一个 Vec” 很好地总结了问题的症结所在。我的想法不是Vec&lt;u8&gt;,而是可能只是一个Vec&lt;RwLockReadGuard&lt;'static, Vec&lt;T&gt;&gt;&gt;,它会被转化为匹配生命周期。也许那不会是神奇的,不是吗?至于性能,对于我的特定应用来说并不是瓶颈,但使用process_fn 处理的实际数据通常只是简单的加法,在同一个循环中不必要的分配看起来很浪费。
  • 是的,从Vec&lt;RwLockReadGuard&lt;'static, Vec&lt;T&gt;&gt;&gt; 转换到Vec&lt;RwLockReadGuard&lt;'static, Vec&lt;T&gt;&gt;&gt; 应该可以;这比使用Vec&lt;u8&gt; 干净得多。这里的主要风险是这会破坏防护装置中内置的安全机制,因为如果我们搞砸了,我们很容易导致防护装置停留的时间超过应有的时间;即,我们最终可能会出现多个写保护共存,或者写保护与读保护共存。但是,如果您确保在转换时向量始终为空,那么我想您是安全的。
  • 不过,我想知道结构化数据的另一种方法是否可以为您提供更好的性能,并且还可以消除对此类技巧的需求。如果你的process_ts 做的工作很少,那么我猜RwLock::read 调用将成为瓶颈。可能有一种方法可以在不使用锁的情况下完成您想做的事情。例如,crate bus (docs.rs/bus/2.2.3/bus) 实现了一个无锁的单生产者、多消费者、广播频道,这可能是一个相关的构建块。
  • 我已根据您的建议对问题进行了重大改写。我对替代方法进行了很多思考,但我不仅在处理数据时,有时还会以随机顺序处理它,甚至对其进行变异,所以我猜这个解决方案适合我的用例。实际上我使用parking_lot::RwLock 并且锁没有竞争,所以锁定本身应该很快。我会考虑改变生命周期,但目前我可能只会在每次需要时分配Vec。不过,非常感谢您提供非常宝贵的反馈。
  • @TomášDvořák FWIW,改变生命周期可能是我会做的(如果我确定 unsafe 代码是合适的)。 Vec&lt;u8&gt; 带来了一个问题,因为 u8RwLockReadGuard&lt;'_, _&gt; 有不同的布局,所以创建 Vec 变得超级很棘手,确保它总是以正确的数量增长和缩小,并在它超出范围时丢弃它而不会破坏东西。 (与转换生命周期并确保您永远不会泄漏对短期事物的长期引用相反,这只是 常规 棘手。)
猜你喜欢
  • 2021-09-21
  • 2011-01-22
  • 1970-01-01
  • 1970-01-01
  • 2012-05-15
  • 1970-01-01
  • 2018-12-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多