mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 08:38:09 -05:00
More sketching
This commit is contained in:
parent
f7a5fea93d
commit
f32b107d73
@ -72,11 +72,7 @@ we'll follow this guide:
|
||||
- Smart pointers hold their contents in the heap
|
||||
- Collections are smart pointers for many objects at a time, and reallocate
|
||||
when they need to grow
|
||||
- Boxed closures (FnBox, others?) are heap allocated
|
||||
- "Move" semantics don't trigger new allocation; just a change of ownership,
|
||||
so are incredibly fast
|
||||
- In examples, is address of data before and after the same?
|
||||
- Can `Copy` trigger allocation?
|
||||
- `lazy_static!` and `thread_local!` force heap allocation
|
||||
- Stack-based alternatives to standard library types should be preferred (spin, parking_lot)
|
||||
|
||||
# Smart pointers
|
||||
@ -96,8 +92,8 @@ crate should look mostly familiar:
|
||||
- [`Arc`](https://doc.rust-lang.org/alloc/sync/struct.Arc.html)
|
||||
- [`Cow`](https://doc.rust-lang.org/alloc/borrow/enum.Cow.html)
|
||||
|
||||
The [standard library](https://doc.rust-lang.org/std/) also defines some smart pointers,
|
||||
though more than can be covered in this article. Some examples:
|
||||
The [standard library](https://doc.rust-lang.org/std/) also defines some smart pointers
|
||||
to manage heap objects, though more than can be covered here. Some examples:
|
||||
- [`RwLock`](https://doc.rust-lang.org/std/sync/struct.RwLock.html)
|
||||
- [`Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html)
|
||||
|
||||
@ -168,4 +164,29 @@ But because the vector has no elements it is managing, no calls to the allocator
|
||||
will ever be dispatched. A couple of places to look at for confirming this behavior:
|
||||
[`Vec::new()`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.new),
|
||||
[`HashMap::new()`](https://doc.rust-lang.org/std/collections/hash_map/struct.HashMap.html#method.new),
|
||||
and [`String::new()`](https://doc.rust-lang.org/std/string/struct.String.html#method.new).
|
||||
and [`String::new()`](https://doc.rust-lang.org/std/string/struct.String.html#method.new).
|
||||
|
||||
# **thread_local!** and **lazy_static!**
|
||||
|
||||
# Heap Alternatives
|
||||
|
||||
While it is a bit strange for us to talk of the stack after spending so much time with the heap,
|
||||
it's worth pointing out that some heap-allocated objects in Rust have stack-based counterparts
|
||||
provided by other crates. There are a number of cases where this may be helpful, so it's useful
|
||||
to know that alternatives exist if you need them.
|
||||
|
||||
When it comes to some of the standard library smart pointers
|
||||
([`RwLock`](https://doc.rust-lang.org/std/sync/struct.RwLock.html) and
|
||||
[`Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html)), stack-based alternatives
|
||||
are provided in crates like [spin](https://crates.io/crates/spin) and
|
||||
[parking_lot](https://crates.io/crates/parking_lot). You can check out
|
||||
[`lock_api::RwLock`](https://docs.rs/lock_api/0.1.5/lock_api/struct.RwLock.html),
|
||||
[`lock_api::Mutex`](https://docs.rs/lock_api/0.1.5/lock_api/struct.Mutex.html), and
|
||||
[`spin::Once`](https://mvdnes.github.io/rust-docs/spin-rs/spin/struct.Once.html)
|
||||
if you're in need of synchronization primitives.
|
||||
|
||||
[thread_id](https://crates.io/crates/thread-id)
|
||||
may still be necessary if you're implementing an allocator (*cough cough* the author *cough cough*)
|
||||
because [`thread::current().id()`](https://doc.rust-lang.org/std/thread/struct.ThreadId.html)
|
||||
[uses a `thread_local!` structure](https://doc.rust-lang.org/stable/src/std/sys_common/thread_info.rs.html#22-40)
|
||||
that needs heap allocation.
|
||||
|
@ -34,6 +34,44 @@ There will, however, be an opera of optimization.
|
||||
|
||||
# The Case of the Disappearing Box
|
||||
|
||||
```rust
|
||||
// Currently doesn't work, not sure why.
|
||||
use std::alloc::{GlobalAlloc, Layout, System};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
fn allocate_box() {
|
||||
let x = Box::new(0);
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
// Turn on panicking if we allocate on the heap
|
||||
DO_PANIC.store(true, Ordering::SeqCst);
|
||||
|
||||
allocate_box();
|
||||
}
|
||||
|
||||
#[global_allocator]
|
||||
static A: PanicAllocator = PanicAllocator;
|
||||
static DO_PANIC: AtomicBool = AtomicBool::new(false);
|
||||
struct PanicAllocator;
|
||||
|
||||
unsafe impl GlobalAlloc for PanicAllocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
if DO_PANIC.load(Ordering::SeqCst) {
|
||||
panic!("Unexpected allocation.");
|
||||
}
|
||||
System.alloc(layout)
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
if DO_PANIC.load(Ordering::SeqCst) {
|
||||
panic!("Unexpected deallocation.");
|
||||
}
|
||||
System.dealloc(ptr, layout);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Vectors of Usual Size
|
||||
|
||||
# Dr. Array or: How I Learned to Love the Optimizer
|
||||
|
@ -98,6 +98,8 @@ With all that in mind, let's talk about situations in which we're guaranteed to
|
||||
- [Arrays](https://doc.rust-lang.org/std/primitive.array.html) are always stack-allocated.
|
||||
- Closures capture their arguments on the stack
|
||||
- Generics will use stack allocation, even with dynamic dispatch.
|
||||
- [`Copy`](https://doc.rust-lang.org/std/marker/trait.Copy.html) types are guaranteed to be
|
||||
stack-allocated, and copying them will be done in stack memory.
|
||||
|
||||
# Structs
|
||||
|
||||
@ -445,4 +447,28 @@ pub fn do_call() {
|
||||
-- [Compiler Explorer](https://godbolt.org/z/u_yguS)
|
||||
|
||||
It's hard to imagine practical situations where dynamic dispatch would be
|
||||
used for objects that aren't heap allocated, but it technically can be done.
|
||||
used for objects that aren't heap allocated, but it technically can be done.
|
||||
|
||||
# Copy types
|
||||
|
||||
Understanding move semantics and copy semantics in Rust is hard. The Rust docs
|
||||
[go into detail](https://doc.rust-lang.org/stable/core/marker/trait.Copy.html)
|
||||
far better than can be addressed here, so I'll leave them to do the job.
|
||||
Their guideline is reasonable though:
|
||||
[if your type can implemement `Copy`, it should](https://doc.rust-lang.org/stable/core/marker/trait.Copy.html#when-should-my-type-be-copy).
|
||||
While there are potential speed tradeoffs to benchmark when discussing `Copy`
|
||||
(move semantics for stack objects vs. copying stack pointers vs. copying stack `struct`s),
|
||||
*it's impossible for `Copy` to introduce a heap allocation*.
|
||||
|
||||
But why is this the case? Fundamentally, it's because the language controls
|
||||
what `Copy` means -
|
||||
["the behavior of `Copy` is not overloadable"](https://doc.rust-lang.org/std/marker/trait.Copy.html#whats-the-difference-between-copy-and-clone)
|
||||
because it's a marker trait. From there we'll note that a type
|
||||
[can implement `Copy`](https://doc.rust-lang.org/std/marker/trait.Copy.html#when-can-my-type-be-copy)
|
||||
if (and only if) its components implement `Copy`, and that
|
||||
[no heap-allocated types implement `Copy`](https://doc.rust-lang.org/std/marker/trait.Copy.html#implementors).
|
||||
Thus, assignments involving heap types are always move semantics, and new heap
|
||||
allocations won't occur without explicit calls to
|
||||
[`clone()`](https://doc.rust-lang.org/std/clone/trait.Clone.html#tymethod.clone).
|
||||
|
||||
TODO: Some examples. Maybe just need to show compiler errors?
|
||||
|
Loading…
Reference in New Issue
Block a user