From f098e330740188f768d7d8b52b922391be04e837 Mon Sep 17 00:00:00 2001 From: Allan Downey Date: Wed, 23 Feb 2022 22:00:28 +0800 Subject: [PATCH] Update: unified format --- .../advance/smart-pointer/cell-refcell.md | 32 +++++++++---------- contents/advance/smart-pointer/rc-arc.md | 13 +++++--- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/contents/advance/smart-pointer/cell-refcell.md b/contents/advance/smart-pointer/cell-refcell.md index 6be832db..e91045c0 100644 --- a/contents/advance/smart-pointer/cell-refcell.md +++ b/contents/advance/smart-pointer/cell-refcell.md @@ -1,7 +1,7 @@ # Cell 和 RefCell Rust 的编译器之严格,可以说是举世无双。特别是在所有权方面,Rust 通过严格的规则来保证所有权和借用的正确性,最终为程序的安全保驾护航。 -但是严格是一把双刃剑,带来安全提升的同时,损失了灵活性,有时甚至会让用户痛苦不堪、怨声载道。因此 Rust 提供了 `Cell` 和 `RefCell` 用于内部可变性,简而言之,可以在拥有不可变引用的同时修改目标数据,对于正常的代码实现来说,这个是不可能做到的(要么一个可变借用,要么多个不可变借用)。 +但是严格是一把双刃剑,带来安全提升的同时,损失了灵活性,有时甚至会让用户痛苦不堪、怨声载道。因此 Rust 提供了 `Cell` 和 `RefCell` 用于内部可变性,简而言之,可以在拥有不可变引用的同时修改目标数据,对于正常的代码实现来说,这个是不可能做到的(要么一个可变借用,要么多个不可变借用)。 > 内部可变性的实现是因为 Rust 使用了 `unsafe` 来做到这一点,但是对于使用者来说,这些都是透明的,因为这些不安全代码都被封装到了安全的 API 中 @@ -14,7 +14,7 @@ fn main() { let one = c.get(); c.set("qwer"); let two = c.get(); - println!("{},{}", one,two); + println!("{},{}", one, two); } ``` @@ -57,7 +57,7 @@ fn main() { let s1 = s.borrow(); let s2 = s.borrow_mut(); - println!("{},{}",s1,s2); + println!("{},{}", s1, s2); } ``` @@ -76,7 +76,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace 而 `RefCell` 正是**用于你确信代码是正确的,而编译器却发生了误判时**。 -对于大型的复杂程序,也可以选择使用 `RefCell` 来让事情简化。例如在 Rust 编译器的[`ctxt结构体`](https://github.com/rust-lang/rust/blob/620d1ee5346bee10ba7ce129b2e20d6e59f0377d/src/librustc/middle/ty.rs#L803-L987)中有大量的 `RefCell` 类型的 `map` 字段, 主要的原因是:这些 `map` 会被分散在各个地方的代码片段所广泛使用或修改。由于这种分散在各处的使用方式,导致了管理可变和不可变成为一件非常复杂的任务(甚至不可能),你很容易就碰到编译器抛出来的各种错误。而且 `RefCell` 的运行时错误在这种情况下也变得非常可爱:一旦有人做了不正确的使用,代码会 `panic`,然后告诉我们哪些借用冲突了。 +对于大型的复杂程序,也可以选择使用 `RefCell` 来让事情简化。例如在 Rust 编译器的[`ctxt结构体`](https://github.com/rust-lang/rust/blob/620d1ee5346bee10ba7ce129b2e20d6e59f0377d/src/librustc/middle/ty.rs#L803-L987)中有大量的 `RefCell` 类型的 `map` 字段,主要的原因是:这些 `map` 会被分散在各个地方的代码片段所广泛使用或修改。由于这种分散在各处的使用方式,导致了管理可变和不可变成为一件非常复杂的任务(甚至不可能),你很容易就碰到编译器抛出来的各种错误。而且 `RefCell` 的运行时错误在这种情况下也变得非常可爱:一旦有人做了不正确的使用,代码会 `panic`,然后告诉我们哪些借用冲突了。 总之,当你确信编译器误报但不知道该如何解决时,或者你有一个引用类型,需要被四处使用和修改然后导致借用关系难以管理时,都可以优先考虑使用 `RefCell`。 @@ -149,7 +149,7 @@ struct MsgQueue { } impl Messenger for MsgQueue { - fn send(&self,msg: String) { + fn send(&self, msg: String) { self.msg_cache.push(msg) } } @@ -229,7 +229,7 @@ RefCell { value: "我很善变,还拥有多个主人, on yeah!" } #### 性能损耗 相信这两者组合在一起使用时,很多人会好奇到底性能如何,下面我们来简单分析下。 -首先给出一个大概的结论,这两者结合在一起使用的性能其实非常高,大致相当于没有线程安全版本的 C++ `std::shared_ptr` 指针,事实上,`C++` 这个指针的主要开销也在于原子性这个并发原语上,毕竟线程安全在哪个语言中开销都不小。 +首先给出一个大概的结论,这两者结合在一起使用的性能其实非常高,大致相当于没有线程安全版本的 C++ `std::shared_ptr` 指针,事实上,C++ 这个指针的主要开销也在于原子性这个并发原语上,毕竟线程安全在哪个语言中开销都不小。 #### 内存损耗 两者结合的数据结构与下面类似: @@ -252,28 +252,28 @@ struct Wrapper { #### CPU 损耗 从CPU来看,损耗如下: -- 对 `Rc` 解引用是免费的(编译期),但是*带来的间接取值并不免费 -- 克隆 `Rc` 需要将当前的引用计数跟 `0` 和 `usize::Max` 进行一次比较,然后将计数值加1 -- 释放 (drop)`Rc` 需要将计数值减1, 然后跟 `0` 进行一次比较 +- 对 `Rc` 解引用是免费的(编译期),但是 `*` 带来的间接取值并不免费 +- 克隆 `Rc` 需要将当前的引用计数跟 `0` 和 `usize::Max` 进行一次比较,然后将计数值加 1 +- 释放(drop) `Rc` 需要将计数值减 1, 然后跟 `0` 进行一次比较 - 对 `RefCell` 进行不可变借用,需要将 `isize` 类型的借用计数加1,然后跟 `0` 进行比较 -- 对 `RefCell `的不可变借用进行释放,需要将 `isize` 减1 -- 对 `RefCell` 的可变借用大致流程跟上面差不多,但是需要先跟 `0` 比较,然后再减1 -- 对 `RefCell` 的可变借用进行释放,需要将 `isize` 加1 +- 对 `RefCell `的不可变借用进行释放,需要将 `isize` 减 1 +- 对 `RefCell` 的可变借用大致流程跟上面差不多,但是需要先跟 `0` 比较,然后再减 1 +- 对 `RefCell` 的可变借用进行释放,需要将 `isize` 加 1 -其实这些细节不必过于关注,只要知道 `CPU` 消耗也非常低,甚至编译器还会对此进行进一步优化! +其实这些细节不必过于关注,只要知道 CPU 消耗也非常低,甚至编译器还会对此进行进一步优化! #### CPU 缓存 Miss -唯一需要担心的可能就是这种组合数据结构对于 `CPU` 缓存是否亲和,这个我们无法证明,只能提出来存在这个可能性,最终的性能影响还需要在实际场景中进行测试。 +唯一需要担心的可能就是这种组合数据结构对于 CPU 缓存是否亲和,这个我们无法证明,只能提出来存在这个可能性,最终的性能影响还需要在实际场景中进行测试。 总之,分析这两者组合的性能还挺复杂的,大概总结下: - 从表面来看,它们带来的内存和 CPU 损耗都不大 -- 但是由于 `Rc` 额外的引入了一次间接取值(*),在少数场景下可能会造成性能上的显著损失 +- 但是由于 `Rc` 额外的引入了一次间接取值(`*`),在少数场景下可能会造成性能上的显著损失 - CPU 缓存可能也不够亲和 ## 通过 `Cell::from_mut` 解决借用冲突 -在 Rust1.37 版本中新增了两个非常实用的方法: +在 Rust 1.37 版本中新增了两个非常实用的方法: - Cell::from_mut,该方法将 `&mut T` 转为 `&Cell` - Cell::as_slice_of_cells,该方法将 `&Cell<[T]>` 转为 `&[Cell]` diff --git a/contents/advance/smart-pointer/rc-arc.md b/contents/advance/smart-pointer/rc-arc.md index 569fb9c1..231cfc5e 100644 --- a/contents/advance/smart-pointer/rc-arc.md +++ b/contents/advance/smart-pointer/rc-arc.md @@ -32,7 +32,7 @@ fn main() { let b = Rc::clone(&a); assert_eq!(2, Rc::strong_count(&a)); - assert_eq!(Rc::strong_count(&a),Rc::strong_count(&b)) + assert_eq!(Rc::strong_count(&a), Rc::strong_count(&b)) } ``` @@ -114,12 +114,15 @@ fn main() { drop(gadget_owner); // 尽管在上面我们释放了 gadget_owner,但是依然可以在这里使用 owner 的信息 - // 原因是在 drop 之前,存在三个指向 Gadget Man 的智能指针引用,上面仅仅 drop 掉其中一个智能指针引用,而不是 drop 掉 owner 数据,外面还有两个引用指向底层的 owner 数据,引用计数尚未清零 + // 原因是在 drop 之前,存在三个指向 Gadget Man 的智能指针引用,上面仅仅 + // drop 掉其中一个智能指针引用,而不是 drop 掉 owner 数据,外面还有两个 + // 引用指向底层的 owner 数据,引用计数尚未清零 // 因此 owner 数据依然可以被使用 println!("Gadget {} owned by {}", gadget1.id, gadget1.owner.name); println!("Gadget {} owned by {}", gadget2.id, gadget2.owner.name); - // 在函数最后,`gadget1` 和 `gadget2` 也被释放,最终引用计数归零,随后底层数据也被清理释放 + // 在函数最后,`gadget1` 和 `gadget2` 也被释放,最终引用计数归零,随后底层 + // 数据也被清理释放 } ``` @@ -145,7 +148,7 @@ fn main() { for _ in 0..10 { let s = Rc::clone(&s); let handle = thread::spawn(move || { - println!("{}",s) + println!("{}", s) }); } } @@ -182,7 +185,7 @@ fn main() { for _ in 0..10 { let s = Arc::clone(&s); let handle = thread::spawn(move || { - println!("{}",s) + println!("{}", s) }); } }