diff --git a/_drafts/a-heaping-helping.md b/_drafts/a-heaping-helping.md index e11546b..3ed807c 100644 --- a/_drafts/a-heaping-helping.md +++ b/_drafts/a-heaping-helping.md @@ -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). \ No newline at end of file +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. diff --git a/_drafts/compiler-optimizations.md b/_drafts/compiler-optimizations.md index 13e6120..753bc8e 100644 --- a/_drafts/compiler-optimizations.md +++ b/_drafts/compiler-optimizations.md @@ -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 diff --git a/_drafts/stacking-up.md b/_drafts/stacking-up.md index 42b3806..519ee55 100644 --- a/_drafts/stacking-up.md +++ b/_drafts/stacking-up.md @@ -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. \ No newline at end of file +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?