如何构建可在 Drop 上重复使用的可变 Vec 池?
How to build a pool of mutable Vecs that get reused on Drop?
我正在尝试创建一个可变 Vec
对象池,这些对象可以根据需要传递给函数,并在不再需要时重用(因为我的目标是 WASM,所以我不想让 Vec
自己解除分配和重新分配)。我有一个使用 Rc
和 RefCell
的实现,我想知道是否有更好(更有效?)的方法来做到这一点。
我当前的代码使用 Rc::strong_count
来跟踪我是否分发了缓冲区,并使用 RefCell
来允许对内部的 Vec
进行可变访问:
use std::{cell::RefCell, rc::Rc};
#[derive(Debug)]
struct BufferPool {
buffers: Vec<Rc<RefCell<Vec<f64>>>>,
buffer_size: usize,
}
impl BufferPool {
fn new() -> Self {
BufferPool {
buffers: vec![],
buffer_size: 3,
}
}
fn add_buffer(&mut self) -> Rc<RefCell<Vec<f64>>> {
self.buffers
.push(Rc::new(RefCell::new(vec![0.; self.buffer_size])));
Rc::clone(&self.buffers[self.buffers.len() - 1])
}
fn get_buffer(&mut self) -> Rc<RefCell<Vec<f64>>> {
for buf in &self.buffers {
// If the Rc count is 1, we haven't loaned the buffer out yet.
if Rc::strong_count(&buf) == 1 {
return Rc::clone(&buf);
}
}
// If we made it here, there's no available buffer, so we need to create one.
self.add_buffer()
}
}
此代码可以通过以下方式测试:
#[test]
fn test_buffers() {
let mut buffers = BufferPool::new();
let buf_cell1 = buffers.get_buffer();
{
let mut buf1 = buf_cell1.borrow_mut();
buf1[0] = 5.5;
}
{
let buf_cell2 = buffers.get_buffer();
let mut buf2 = buf_cell2.borrow_mut();
buf2[1] = 6.6;
}
{
let buf_cell3 = buffers.get_buffer();
let mut buf3 = buf_cell3.borrow_mut();
buf3[2] = 7.7;
}
dbg!(&buffers);
}
给出了预期的输出:
&buffers = BufferPool {
buffers: [
RefCell {
value: [
5.5,
0.0,
0.0,
],
},
RefCell {
value: [
0.0,
6.6,
7.7,
],
},
],
buffer_size: 3,
}
但是,我正在做的事情似乎有点低效,因为 Rc
和 RefCell::borrow_mut()
都在跟踪缓冲区是否已“借出”(因为 RefCell
有如果它的内容被双重借用,则能够出错)。此外,从人体工程学的角度来看,令人讨厌的是我无法在一条线上调用 buffers.get_buffer().borrow_mut()
而 Rust 会抱怨临时值丢失。
所以,我的问题是:有没有更好的方法来做到这一点?
如您所见,通过 Rc<RefCell<T>>
提供对对象的访问是可行的,但不太符合人体工程学。设计池的更好方法是 return 包装器 取得值的所有权 ,然后在丢弃时将其放回池中。这是如何做到这一点的“基本”示例:
use std::cell::RefCell;
use std::ops::{Deref, DerefMut};
#[derive(Debug)]
struct BufferPool {
buffers: RefCell<Vec<Vec<f32>>>,
buffer_size: usize,
}
impl BufferPool {
pub fn new() -> Self {
BufferPool {
buffers: RefCell::new(Vec::new()),
buffer_size: 3,
}
}
pub fn get_buffer(&self) -> BufferPoolRef {
let mut buffers = self.buffers.borrow_mut();
let buffer = buffers.pop().unwrap_or_else(|| vec![0.0; self.buffer_size]);
BufferPoolRef { pool: self, buffer }
}
fn return_buffer(&self, buffer: Vec<f32>) {
let mut buffers = self.buffers.borrow_mut();
buffers.push(buffer);
}
}
struct BufferPoolRef<'a> {
pool: &'a BufferPool,
buffer: Vec<f32>,
}
impl Deref for BufferPoolRef<'_> {
type Target = Vec<f32>;
fn deref(&self) -> &Self::Target {
&self.buffer
}
}
impl DerefMut for BufferPoolRef<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer
}
}
impl Drop for BufferPoolRef<'_> {
fn drop(&mut self) {
let buffer = std::mem::take(&mut self.buffer);
self.pool.return_buffer(buffer);
}
}
fn main() {
let mut buffers = BufferPool::new();
let mut buf1 = buffers.get_buffer();
{
buf1[0] = 5.5;
}
{
let mut buf2 = buffers.get_buffer();
buf2[1] = 6.6;
}
{
let mut buf3 = buffers.get_buffer();
buf3[2] = 7.7;
}
drop(buf1);
dbg!(&buffers);
}
[src/main.rs:71] &buffers = BufferPool {
buffers: RefCell {
value: [
[
0.0,
6.6,
7.7,
],
[
5.5,
0.0,
0.0,
],
],
},
buffer_size: 3,
}
但是,您可以使用像 object-pool or lifeguard 这样的板条箱,而不是自己做所有这些。他们都在 WASM 上工作并使用我上面描述的机制。这是基于对象池的 BufferPool
的实现:
use object_pool::{Pool, Reusable};
struct BufferPool {
pool: Pool<Vec<f32>>,
buffer_size: usize,
}
impl BufferPool {
pub fn new() -> Self {
BufferPool {
pool: Pool::new(2, || vec![0.0; 3]),
buffer_size: 3,
}
}
pub fn get_buffer(&self) -> Reusable<Vec<f32>> {
self.pool.pull(|| vec![0.0; self.buffer_size])
}
}
我正在尝试创建一个可变 Vec
对象池,这些对象可以根据需要传递给函数,并在不再需要时重用(因为我的目标是 WASM,所以我不想让 Vec
自己解除分配和重新分配)。我有一个使用 Rc
和 RefCell
的实现,我想知道是否有更好(更有效?)的方法来做到这一点。
我当前的代码使用 Rc::strong_count
来跟踪我是否分发了缓冲区,并使用 RefCell
来允许对内部的 Vec
进行可变访问:
use std::{cell::RefCell, rc::Rc};
#[derive(Debug)]
struct BufferPool {
buffers: Vec<Rc<RefCell<Vec<f64>>>>,
buffer_size: usize,
}
impl BufferPool {
fn new() -> Self {
BufferPool {
buffers: vec![],
buffer_size: 3,
}
}
fn add_buffer(&mut self) -> Rc<RefCell<Vec<f64>>> {
self.buffers
.push(Rc::new(RefCell::new(vec![0.; self.buffer_size])));
Rc::clone(&self.buffers[self.buffers.len() - 1])
}
fn get_buffer(&mut self) -> Rc<RefCell<Vec<f64>>> {
for buf in &self.buffers {
// If the Rc count is 1, we haven't loaned the buffer out yet.
if Rc::strong_count(&buf) == 1 {
return Rc::clone(&buf);
}
}
// If we made it here, there's no available buffer, so we need to create one.
self.add_buffer()
}
}
此代码可以通过以下方式测试:
#[test]
fn test_buffers() {
let mut buffers = BufferPool::new();
let buf_cell1 = buffers.get_buffer();
{
let mut buf1 = buf_cell1.borrow_mut();
buf1[0] = 5.5;
}
{
let buf_cell2 = buffers.get_buffer();
let mut buf2 = buf_cell2.borrow_mut();
buf2[1] = 6.6;
}
{
let buf_cell3 = buffers.get_buffer();
let mut buf3 = buf_cell3.borrow_mut();
buf3[2] = 7.7;
}
dbg!(&buffers);
}
给出了预期的输出:
&buffers = BufferPool {
buffers: [
RefCell {
value: [
5.5,
0.0,
0.0,
],
},
RefCell {
value: [
0.0,
6.6,
7.7,
],
},
],
buffer_size: 3,
}
但是,我正在做的事情似乎有点低效,因为 Rc
和 RefCell::borrow_mut()
都在跟踪缓冲区是否已“借出”(因为 RefCell
有如果它的内容被双重借用,则能够出错)。此外,从人体工程学的角度来看,令人讨厌的是我无法在一条线上调用 buffers.get_buffer().borrow_mut()
而 Rust 会抱怨临时值丢失。
所以,我的问题是:有没有更好的方法来做到这一点?
如您所见,通过 Rc<RefCell<T>>
提供对对象的访问是可行的,但不太符合人体工程学。设计池的更好方法是 return 包装器 取得值的所有权 ,然后在丢弃时将其放回池中。这是如何做到这一点的“基本”示例:
use std::cell::RefCell;
use std::ops::{Deref, DerefMut};
#[derive(Debug)]
struct BufferPool {
buffers: RefCell<Vec<Vec<f32>>>,
buffer_size: usize,
}
impl BufferPool {
pub fn new() -> Self {
BufferPool {
buffers: RefCell::new(Vec::new()),
buffer_size: 3,
}
}
pub fn get_buffer(&self) -> BufferPoolRef {
let mut buffers = self.buffers.borrow_mut();
let buffer = buffers.pop().unwrap_or_else(|| vec![0.0; self.buffer_size]);
BufferPoolRef { pool: self, buffer }
}
fn return_buffer(&self, buffer: Vec<f32>) {
let mut buffers = self.buffers.borrow_mut();
buffers.push(buffer);
}
}
struct BufferPoolRef<'a> {
pool: &'a BufferPool,
buffer: Vec<f32>,
}
impl Deref for BufferPoolRef<'_> {
type Target = Vec<f32>;
fn deref(&self) -> &Self::Target {
&self.buffer
}
}
impl DerefMut for BufferPoolRef<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer
}
}
impl Drop for BufferPoolRef<'_> {
fn drop(&mut self) {
let buffer = std::mem::take(&mut self.buffer);
self.pool.return_buffer(buffer);
}
}
fn main() {
let mut buffers = BufferPool::new();
let mut buf1 = buffers.get_buffer();
{
buf1[0] = 5.5;
}
{
let mut buf2 = buffers.get_buffer();
buf2[1] = 6.6;
}
{
let mut buf3 = buffers.get_buffer();
buf3[2] = 7.7;
}
drop(buf1);
dbg!(&buffers);
}
[src/main.rs:71] &buffers = BufferPool {
buffers: RefCell {
value: [
[
0.0,
6.6,
7.7,
],
[
5.5,
0.0,
0.0,
],
],
},
buffer_size: 3,
}
但是,您可以使用像 object-pool or lifeguard 这样的板条箱,而不是自己做所有这些。他们都在 WASM 上工作并使用我上面描述的机制。这是基于对象池的 BufferPool
的实现:
use object_pool::{Pool, Reusable};
struct BufferPool {
pool: Pool<Vec<f32>>,
buffer_size: usize,
}
impl BufferPool {
pub fn new() -> Self {
BufferPool {
pool: Pool::new(2, || vec![0.0; 3]),
buffer_size: 3,
}
}
pub fn get_buffer(&self) -> Reusable<Vec<f32>> {
self.pool.pull(|| vec![0.0; self.buffer_size])
}
}