我正在实施康威的生命游戏来自学 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
and R
主要是避免为不同的访问模式编写不同的方法的一种方法。
The iter_mut()
方法返回一个BoardIter
struct 是 cells 向量的可变切片迭代器的包装器,因此具有&mut CellRW
as Item
类型。也了解到目前的情况BoardPos
(x 和 y 坐标,偏移量)。
我想我会迭代所有单元元组,跟踪坐标,计算每个(读取)单元的活动邻居的数量(我需要知道为此的坐标/偏移量),计算下一代的单元状态并写入元组各自的另一半。
当然,最后编译器向我展示了我的设计中的致命缺陷,正如我借用的self.board
可变地在iter_mut()
方法,然后尝试再次不可变地借用它以获取读取单元的所有邻居。
到目前为止我还没有能够想出一个好的解决方案来解决这个问题。我确实设法让它工作
引用不可变,然后使用UnsafeCell
将对写入单元的不可变引用转变为可变引用。
然后,我通过以下方式写入对元组写入部分的名义上不可变的引用:UnsafeCell
。
然而,这并不让我觉得是一个合理的设计,我怀疑在尝试并行化事物时可能会遇到问题。
有没有一种方法可以实现我在安全/惯用的 Rust 中提出的数据布局,或者这实际上是您必须使用技巧来规避 Rust 的别名/借用限制的情况?
另外,作为一个更广泛的问题,是否存在需要规避 Rust 借用限制的问题的可识别模式?