【发布时间】:2018-05-20 22:48:46
【问题描述】:
我正在实施 Conway 的生活游戏来自学 Rust。这个想法是先实现一个单线程版本,尽可能优化它,然后对多线程版本做同样的事情。
我想实现另一种数据布局,我认为它可能对缓存更友好。这个想法是将板上每个点的两个单元的状态存储在一个向量中,一个单元用于读取当前一代的状态,一个用于写入下一代的状态,交替访问模式每个 生成的计算(可以在编译时确定)。
基本数据结构如下:
#[repr(u8)]
pub enum CellStatus {
DEAD,
ALIVE,
}
/** 2 bytes */
pub struct CellRW(CellStatus, CellStatus);
pub struct TupleBoard {
width: usize,
height: usize,
cells: Vec<CellRW>,
}
/** used to keep track of current pos with iterator e.g. */
pub struct BoardPos {
x_pos: usize,
y_pos: usize,
offset: usize,
}
pub struct BoardEvo {
board: TupleBoard,
}
给我带来麻烦的功能:
impl BoardEvo {
fn evolve_step<T: RWSelector>(&mut self) {
for (pos, cell) in self.board.iter_mut() {
//pos: BoardPos, cell: &mut CellRW
let read: &CellStatus = T::read(cell); //chooses the right tuple half for the current evolution step
let write: &mut CellStatus = T::write(cell);
let alive_count = pos.neighbours::<T>(&self.board).iter() //<- can't borrow self.board again!
.filter(|&&status| status == CellStatus::ALIVE)
.count();
*write = CellStatus::evolve(*read, alive_count);
}
}
}
impl BoardPos {
/* ... */
pub fn neighbours<T: RWSelector>(&self, board: &BoardTuple) -> [CellStatus; 8] {
/* ... */
}
}
特征RWSelector 具有用于读取和写入单元元组(CellRW)的静态函数。它针对L 和R 两种零大小类型实现,主要是避免为不同的访问模式编写不同的方法。
iter_mut() 方法返回一个 BoardIter 结构体,它是单元格向量的可变切片迭代器的包装器,因此具有 &mut CellRW 作为 Item 类型。它还知道当前的BoardPos(x 和 y 坐标,偏移量)。
我以为我会遍历所有单元元组,跟踪坐标,计算每个(读取)单元的存活邻居的数量(我需要知道坐标/偏移量),计算单元的状态下一代并写入元组的另一半。
当然,最后,编译器向我展示了我设计中的致命缺陷,因为我在iter_mut() 方法中可变地借用self.board,然后尝试再次不可变地借用它以获取读取的所有邻居细胞。
到目前为止,我还没有为这个问题想出一个好的解决方案。我确实设法通过使所有
引用不可变,然后使用UnsafeCell 将对写入单元格的不可变引用转换为可变引用。
然后我通过UnsafeCell 写入元组写入部分的名义上不可变的引用。
然而,这并没有让我印象深刻的设计,我怀疑我在尝试并行化事情时可能会遇到这个问题。
有没有办法实现我在安全/惯用的 Rust 中提出的数据布局,或者这实际上是您必须使用技巧来规避 Rust 的别名/借用限制的情况?
另外,作为一个更广泛的问题,对于需要您绕过 Rust 的借用限制的问题,是否存在可识别的模式?
【问题讨论】:
-
使用
UnsafeCell——你为什么一直去UnsafeCell而不是例如只是一个Cell?
标签: rust