Rust 何时有必要避免生锈';谁是借书人?
我正在实施康威的人生游戏来教会自己。其思想是首先实现单线程版本,尽可能地优化它,然后对多线程版本执行相同的操作 我想实现一个替代的数据布局,我认为它可能对缓存更友好。其思想是将电路板上每个点的两个单元的状态存储在向量中,一个用于读取当前一代的状态,另一个用于写入下一代的状态,交替每个单元的访问模式 生成的计算(可在编译时确定) 基本数据结构如下:Rust 何时有必要避免生锈';谁是借书人?,rust,Rust,我正在实施康威的人生游戏来教会自己。其思想是首先实现单线程版本,尽可能地优化它,然后对多线程版本执行相同的操作 我想实现一个替代的数据布局,我认为它可能对缓存更友好。其思想是将电路板上每个点的两个单元的状态存储在向量中,一个用于读取当前一代的状态,另一个用于写入下一代的状态,交替每个单元的访问模式 生成的计算(可在编译时确定) 基本数据结构如下: #[repr(u8)] pub enum CellStatus { DEAD, ALIVE, } /** 2 bytes */ pu
#[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,
}
#[repr(u8)]
发布枚举单元状态{
死去的
活着的
}
/**2字节*/
发布结构CellRW(CellStatus,CellStatus);
pub结构元组板{
宽度:usize,
高度:usize,
细胞:Vec,
}
/**用于使用迭代器跟踪当前pos,例如*/
博德波斯酒店{
x_pos:usize,
y_pos:usize,
抵销:usize,
}
BoardEvo酒店{
董事会:元宝板,
}
给我带来麻烦的功能:
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] {
/* ... */
}
}
impl BoardEvo{
fn演进步骤(&mut self){
用于self.board.iter_mut()中的(位置、单元){
//位置:BoardPos、cell:&多个CellRW
let read:&CellStatus=T::read(cell);//为当前演化步骤选择正确的一半元组
let write:&mut CellStatus=T::write(cell);
let alive_count=pos.neights::(&self.board).iter()//
什么时候有必要绕过Rust的借货检查器
在下列情况下需要:
- 借用检查器不够先进,无法确保您的使用安全
- 您不希望(或不能)以不同的模式编写代码
具体来说,编译器无法判断这是安全的:
let mut array = [1, 2];
let a = &mut array[0];
let b = &mut array[1];
编译器不知道片的IndexMut
实现在编译时做了什么(这是一个深思熟虑的设计选择)。据它所知,数组总是返回完全相同的引用,而不管索引参数是什么。我们可以断定此代码是安全的,但编译器不允许它
您可以用一种显然对编译器安全的方式重写此代码:
let mut array = [1, 2];
let (a, b) = array.split_at_mut(1);
let a = &mut a[0];
let b = &mut b[0];
如何做到这一点?为了确保它实际上是安全的:
fn在mut(&mut self,mid:usize)->(&mut[T],&mut[T]){
设len=self.len();
设ptr=self.as_mut_ptr();
不安全{
断言!(正在使用一个UnsafeCell
-为什么你要一直使用UnsafeCell
,而不是只使用一个单元格
?谢谢你的深入解释。很有趣,我没有想过使用一个简单的索引循环。我现在不能测试,但这应该是解决我问题的一种方法。而且我只浏览了ch当我尝试std::mem::转换我的不可变引用时,编译器让我想到了UnsafeCell。实现了两个不同的版本,一个带有索引循环,另一个带有std::cell::cell使用内部可变性,从一些初步的基准测试来看,使用索引循环与我以前的实现相比,使用内部可变性的速度要快得多,速度要慢得多。我以前的实现使用了一个不安全的单元格来更改值,不太清楚为什么会这样,但这很有趣。@Oliver这对我来说实际上是非常反直觉的。cell
在索引一个rray应该有一个(微小的)越界检查。你确实用--release
或cargo bench
运行了你的基准测试,对吗?是的,我做了。我也尽了最大努力,使不同的实现尽可能不存在差异,所以实现a)使用两个索引循环(x,y)和未选中的要访问每个单元格和实现,b)使用单元格和坐标感知迭代器。当我使用两个带常规get_mut()访问的索引循环时,结果更加接近,但循环仍然获胜。我不知道为什么会这样,但我假设一个版本可能允许更多的编译器优化。
fn split_at_mut(&mut self, mid: usize) -> (&mut [T], &mut [T]) {
let len = self.len();
let ptr = self.as_mut_ptr();
unsafe {
assert!(mid <= len);
(from_raw_parts_mut(ptr, mid),
from_raw_parts_mut(ptr.offset(mid as isize), len - mid))
}
}