mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 16:48:10 -05:00
Finish up all the code sections
Need to do some final writing, cleanup, and then publish on Monday?
This commit is contained in:
parent
05e0f68c23
commit
31af7290ba
@ -12,18 +12,21 @@ how the language uses dynamic memory (also referred to as the **heap**) is a sys
|
|||||||
And as the docs mention, ownership
|
And as the docs mention, ownership
|
||||||
[is Rust's most unique feature](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html).
|
[is Rust's most unique feature](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html).
|
||||||
|
|
||||||
The heap is used in two situations; when the compiler is unable to predict either the *total size
|
The heap is used in two situations: when the compiler is unable to predict the *total size
|
||||||
of memory needed*, or *how long the memory is needed for*, it will allocate space in the heap.
|
of memory needed*, or *how long the memory is needed for*, it will allocate space in the heap.
|
||||||
This happens pretty frequently; if you want to download the Google home page, you won't know
|
This happens pretty frequently; if you want to download the Google home page, you won't know
|
||||||
how large it is until your program runs. And when you're finished with Google, whenever that might be,
|
how large it is until your program runs. And when you're finished with Google, whenever that
|
||||||
we deallocate the memory so it can be used to store other webpages.
|
happens to be, we deallocate the memory so it can be used to store other webpages. If you're
|
||||||
|
interested in a slightly longer explanation of the heap, check out
|
||||||
|
[The Stack and the Heap](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html#the-stack-and-the-heap)
|
||||||
|
in Rust's documentation.
|
||||||
|
|
||||||
We won't go into detail on how the heap is managed; the
|
We won't go into detail on how the heap is managed; the
|
||||||
[ownership documentation](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html)
|
[ownership documentation](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html)
|
||||||
does a phenomenal job explaining both the "why" and "how" of memory management. Instead,
|
does a phenomenal job explaining both the "why" and "how" of memory management. Instead,
|
||||||
we're going to focus on understanding "when" heap allocations occur in Rust.
|
we're going to focus on understanding "when" heap allocations occur in Rust.
|
||||||
|
|
||||||
To start off: take a guess for how many allocations happen in the program below:
|
To start off, take a guess for how many allocations happen in the program below:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn main() {}
|
fn main() {}
|
||||||
@ -72,8 +75,11 @@ we'll follow this guide:
|
|||||||
- Smart pointers hold their contents in the heap
|
- Smart pointers hold their contents in the heap
|
||||||
- Collections are smart pointers for many objects at a time, and reallocate
|
- Collections are smart pointers for many objects at a time, and reallocate
|
||||||
when they need to grow
|
when they need to grow
|
||||||
- `lazy_static!` and `thread_local!` force heap allocation for everything.
|
|
||||||
- Stack-based alternatives to standard library types should be preferred (spin, parking_lot)
|
Finally, there are two "addendum" issues that are important to address when discussing
|
||||||
|
Rust and the heap:
|
||||||
|
- Stack-based alternatives to some standard library types are available
|
||||||
|
- Special allocators to track memory behavior are available
|
||||||
|
|
||||||
# Smart pointers
|
# Smart pointers
|
||||||
|
|
||||||
@ -98,10 +104,10 @@ to manage heap objects, though more than can be covered here. Some examples:
|
|||||||
- [`Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html)
|
- [`Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html)
|
||||||
|
|
||||||
Finally, there is one ["gotcha"](https://www.merriam-webster.com/dictionary/gotcha):
|
Finally, there is one ["gotcha"](https://www.merriam-webster.com/dictionary/gotcha):
|
||||||
cell types (like [`RefCell`](https://doc.rust-lang.org/stable/core/cell/struct.RefCell.html))
|
**cell types** (like [`RefCell`](https://doc.rust-lang.org/stable/core/cell/struct.RefCell.html))
|
||||||
follow the RAII pattern, but don't involve heap allocation. Check out the
|
look and behave similarly, but **don't involve heap allocation**. The
|
||||||
[`core::cell` docs](https://doc.rust-lang.org/stable/core/cell/index.html)
|
[`core::cell` docs](https://doc.rust-lang.org/stable/core/cell/index.html)
|
||||||
for more information.
|
have more information.
|
||||||
|
|
||||||
When a smart pointer is created, the data it is given is placed in heap memory and
|
When a smart pointer is created, the data it is given is placed in heap memory and
|
||||||
the location of that data is recorded in the smart pointer. Once the smart pointer
|
the location of that data is recorded in the smart pointer. Once the smart pointer
|
||||||
@ -117,40 +123,43 @@ use std::sync::Arc;
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub fn my_box() {
|
pub fn my_box() {
|
||||||
// Drop at line 1640
|
// Drop at assembly line 1640
|
||||||
Box::new(0);
|
Box::new(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn my_rc() {
|
pub fn my_rc() {
|
||||||
// Drop at line 1650
|
// Drop at assembly line 1650
|
||||||
Rc::new(0);
|
Rc::new(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn my_arc() {
|
pub fn my_arc() {
|
||||||
// Drop at line 1660
|
// Drop at assembly line 1660
|
||||||
Arc::new(0);
|
Arc::new(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn my_cow() {
|
pub fn my_cow() {
|
||||||
// Drop at line 1672
|
// Drop at assembly line 1672
|
||||||
Cow::from("drop");
|
Cow::from("drop");
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
-- [Compiler Explorer](https://godbolt.org/z/SaDpWg)
|
-- [Compiler Explorer](https://godbolt.org/z/4AMQug)
|
||||||
|
|
||||||
# Collections
|
# Collections
|
||||||
|
|
||||||
Collections types use heap memory because they have dynamic size; they will request more memory
|
Collections types use heap memory because their contents have dynamic size; they will request
|
||||||
[when needed](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.reserve),
|
more memory [when needed](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.reserve),
|
||||||
and can [release memory](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.shrink_to_fit)
|
and can [release memory](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.shrink_to_fit)
|
||||||
when it's no longer necessary. This dynamic memory usage forces Rust to heap allocate
|
when it's no longer necessary. This dynamic property forces Rust to heap allocate
|
||||||
everything they contain. In a way, **collections are smart pointers for many objects at once.**
|
everything they contain. In a way, **collections are smart pointers for many objects at once.**
|
||||||
Common types that fall under this umbrella are `Vec`, `HashMap`, and `String`
|
Common types that fall under this umbrella are
|
||||||
|
[`Vec`](https://doc.rust-lang.org/stable/alloc/vec/struct.Vec.html),
|
||||||
|
[`HashMap`](https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html), and
|
||||||
|
[`String`](https://doc.rust-lang.org/stable/alloc/string/struct.String.html)
|
||||||
(not [`&str`](https://doc.rust-lang.org/std/primitive.str.html)).
|
(not [`&str`](https://doc.rust-lang.org/std/primitive.str.html)).
|
||||||
|
|
||||||
But while collections store the objects they own in heap memory, *creating new collections
|
But while collections store the objects they own in heap memory, *creating new collections
|
||||||
will not allocate on the heap*. This is a bit weird, because if we call `Vec::new()` the
|
will not allocate on the heap*. This is a bit weird; if we call `Vec::new()`, the
|
||||||
assembly shows a corresponding call to `drop_in_place`:
|
assembly shows a corresponding call to `real_drop_in_place`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub fn my_vec() {
|
pub fn my_vec() {
|
||||||
@ -161,27 +170,58 @@ pub fn my_vec() {
|
|||||||
-- [Compiler Explorer](https://godbolt.org/z/1WkNtC)
|
-- [Compiler Explorer](https://godbolt.org/z/1WkNtC)
|
||||||
|
|
||||||
But because the vector has no elements it is managing, no calls to the allocator
|
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:
|
will ever be dispatched:
|
||||||
[`Vec::new()`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.new),
|
|
||||||
|
```rust
|
||||||
|
use std::alloc::{GlobalAlloc, Layout, System};
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Turn on panicking if we allocate on the heap
|
||||||
|
DO_PANIC.store(true, Ordering::SeqCst);
|
||||||
|
|
||||||
|
// Interesting bit happens here
|
||||||
|
let x: Vec<u8> = Vec::new();
|
||||||
|
drop(x);
|
||||||
|
|
||||||
|
// Turn panicking back off, some deallocations occur
|
||||||
|
// after main as well.
|
||||||
|
DO_PANIC.store(false, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
-- [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=831a297d176d015b1f9ace01ae416cc6)
|
||||||
|
|
||||||
|
Other standard library types follow the same behavior; make sure to check out
|
||||||
[`HashMap::new()`](https://doc.rust-lang.org/std/collections/hash_map/struct.HashMap.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).
|
||||||
|
|
||||||
# **lazy_static!** and **thread_local!**
|
|
||||||
|
|
||||||
There are two macros worth addressing in a conversation about heap memory. The first isn't part
|
|
||||||
of the standard library, but it's the [5th most downloaded crate](https://crates.io/crates/lazy_static)
|
|
||||||
in Rust. The second
|
|
||||||
|
|
||||||
TODO: Not so sure about lazy_static anymore. Is thread_local possibly heap-allocated too?
|
|
||||||
- Think it may actually be that lazy_static has a no_std mode that uses `spin`, std-mode uses std::Once.
|
|
||||||
- Reasonably confident thread_local always allocates
|
|
||||||
|
|
||||||
# Heap Alternatives
|
# Heap Alternatives
|
||||||
|
|
||||||
While it is a bit strange for us to talk of the stack after spending so much time with the heap,
|
While it is a bit strange for us to talk of the stack after spending time with the heap,
|
||||||
it's worth pointing out that some heap-allocated objects in Rust have stack-based counterparts
|
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
|
provided by other crates. If you have need of the functionality, but want to avoid allocating,
|
||||||
to know that alternatives exist if you need them.
|
there are some great alternatives.
|
||||||
|
|
||||||
When it comes to some of the standard library smart pointers
|
When it comes to some of the standard library smart pointers
|
||||||
([`RwLock`](https://doc.rust-lang.org/std/sync/struct.RwLock.html) and
|
([`RwLock`](https://doc.rust-lang.org/std/sync/struct.RwLock.html) and
|
||||||
@ -198,3 +238,15 @@ may still be necessary if you're implementing an allocator (*cough cough* the au
|
|||||||
because [`thread::current().id()`](https://doc.rust-lang.org/std/thread/struct.ThreadId.html)
|
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)
|
[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.
|
that needs heap allocation.
|
||||||
|
|
||||||
|
# Tracing Allocators
|
||||||
|
|
||||||
|
When writing performance-sensitive code, there's no alternative to measuring your code.
|
||||||
|
[Measure first](https://youtu.be/nXaxk27zwlk?t=583), because you should never rely on
|
||||||
|
your instincts when [a microsecond is an eternity](https://www.youtube.com/watch?v=NH1Tta7purM).
|
||||||
|
|
||||||
|
Similarly, there's great work going on in Rust with allocators that keep track of what
|
||||||
|
they're doing. [`alloc_counter`](https://crates.io/crates/alloc_counter) was designed
|
||||||
|
for exactly this purpose. When it comes to tracking heap behavior, you shouldn't just
|
||||||
|
rely on the language; please measure and make sure that you have tools in place to catch
|
||||||
|
any issues that come up.
|
||||||
|
@ -19,13 +19,13 @@ where we throw out all the rules and take the kid gloves off. As it turns out,
|
|||||||
both the Rust compiler and the LLVM optimizers are incredibly sophisticated,
|
both the Rust compiler and the LLVM optimizers are incredibly sophisticated,
|
||||||
and we'll step back and let them do their job.
|
and we'll step back and let them do their job.
|
||||||
|
|
||||||
|
Similar to ["What Has My Compiler Done For Me Lately?"](https://www.youtube.com/watch?v=bSkpMdDe4g4),
|
||||||
|
we're focusing on interesting things the Rust language (and LLVM!) can do.
|
||||||
We'll still be looking at assembly code to understand what's going on,
|
We'll still be looking at assembly code to understand what's going on,
|
||||||
but it's important to mention again: **please use automated tools like
|
but it's important to mention again: **please use automated tools like
|
||||||
[qadapt](https://crates.io/crates/qadapt) to double-check memory behavior**.
|
[alloc-counter](https://crates.io/crates/alloc_counter) to double-check memory behavior**.
|
||||||
It's far too easy to mis-read assembly in large code sections, you should
|
It's far too easy to mis-read assembly in large code sections, you should
|
||||||
always have an automated tool verify behavior if you care about memory usage.
|
always have an automated tool verify behavior if you care about memory usage.
|
||||||
Similar to ["What Has My Compiler Done For Me Lately?"](https://www.youtube.com/watch?v=bSkpMdDe4g4),
|
|
||||||
we're just focused on interesting things the Rust language can do.
|
|
||||||
|
|
||||||
The guiding principal as we move forward is this: *optimizing compilers
|
The guiding principal as we move forward is this: *optimizing compilers
|
||||||
won't produce worse assembly than we started with.* There won't be any
|
won't produce worse assembly than we started with.* There won't be any
|
||||||
@ -35,19 +35,24 @@ There will, however, be an opera of optimization.
|
|||||||
# The Case of the Disappearing Box
|
# The Case of the Disappearing Box
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Currently doesn't work, not sure why.
|
|
||||||
use std::alloc::{GlobalAlloc, Layout, System};
|
use std::alloc::{GlobalAlloc, Layout, System};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
fn allocate_box() {
|
fn allocate_box() {
|
||||||
let x = Box::new(0);
|
let _x = Box::new(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
// Turn on panicking if we allocate on the heap
|
// Turn on panicking if we allocate on the heap
|
||||||
DO_PANIC.store(true, Ordering::SeqCst);
|
DO_PANIC.store(true, Ordering::SeqCst);
|
||||||
|
|
||||||
|
// This code will only run with the mode set to "Release".
|
||||||
|
// If you try running in "Debug", you'll get a panic.
|
||||||
allocate_box();
|
allocate_box();
|
||||||
|
|
||||||
|
// Turn off panicking, as there are some deallocations
|
||||||
|
// when we exit main.
|
||||||
|
DO_PANIC.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
@ -71,7 +76,81 @@ unsafe impl GlobalAlloc for PanicAllocator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
-- [Rust Playground](https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=3fe2846dac6755dbb7bb90342d0bf135)
|
||||||
|
|
||||||
# Vectors of Usual Size
|
# Vectors of Usual Size
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::alloc::{GlobalAlloc, Layout, System};
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Turn on panicking if we allocate on the heap
|
||||||
|
DO_PANIC.store(true, Ordering::SeqCst);
|
||||||
|
|
||||||
|
// If the compiler can predict how large a vector will be,
|
||||||
|
// it can optimize out the heap storage needed.
|
||||||
|
let x: Vec<u64> = Vec::with_capacity(5);
|
||||||
|
drop(x);
|
||||||
|
|
||||||
|
// Turn off panicking, as there are some deallocations
|
||||||
|
// when we exit main.
|
||||||
|
DO_PANIC.store(false, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
-- [Rust Playground](https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=5e9761b63243018d094829d901dd85c4)
|
||||||
|
|
||||||
# Dr. Array or: How I Learned to Love the Optimizer
|
# Dr. Array or: How I Learned to Love the Optimizer
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Default)]
|
||||||
|
struct TwoFiftySix {
|
||||||
|
_a: [u64; 32]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct EightK {
|
||||||
|
_a: [TwoFiftySix; 32]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct TwoFiftySixK {
|
||||||
|
_a: [EightK; 32]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct EightM {
|
||||||
|
_a: [TwoFiftySixK; 32]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
// Normally this blows up because we can't reserve size on stack
|
||||||
|
// for the `EightM` struct. But because the compiler notices we
|
||||||
|
// never do anything with `_x`, it optimizes out the stack storage
|
||||||
|
// and the program completes successfully.
|
||||||
|
let _x = EightM::default();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
-- [Compiler Explorer](https://godbolt.org/z/daHn7P)
|
||||||
|
-- [Rust Playground](https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=4c253bf26072119896ab93c6ef064dc0)
|
||||||
|
@ -42,16 +42,16 @@ the faster stack-based allocation for variables.
|
|||||||
With that in mind, let's get into the details. How do we know when Rust will or will not use
|
With that in mind, let's get into the details. How do we know when Rust will or will not use
|
||||||
stack allocation for objects we create? Looking at other languages, it's often easy to delineate
|
stack allocation for objects we create? Looking at other languages, it's often easy to delineate
|
||||||
between stack and heap. Managed memory languages (Python, Java,
|
between stack and heap. Managed memory languages (Python, Java,
|
||||||
[C#](https://blogs.msdn.microsoft.com/ericlippert/2010/09/30/the-truth-about-value-types/)) assume
|
[C#](https://blogs.msdn.microsoft.com/ericlippert/2010/09/30/the-truth-about-value-types/))
|
||||||
everything is on the heap. JIT compilers ([PyPy](https://www.pypy.org/),
|
place everything on the heap. JIT compilers ([PyPy](https://www.pypy.org/),
|
||||||
[HotSpot](https://www.oracle.com/technetwork/java/javase/tech/index-jsp-136373.html)) may
|
[HotSpot](https://www.oracle.com/technetwork/java/javase/tech/index-jsp-136373.html)) may
|
||||||
optimize some heap allocations away, but you should never assume it will happen.
|
optimize some heap allocations away, but you should never assume it will happen.
|
||||||
C makes things clear with calls to special functions ([malloc(3)](https://linux.die.net/man/3/malloc)
|
C makes things clear with calls to special functions ([malloc(3)](https://linux.die.net/man/3/malloc)
|
||||||
is one) being the way to use heap memory. Old C++ has the [`new`](https://stackoverflow.com/a/655086/1454178)
|
is one) being the way to use heap memory. Old C++ has the [`new`](https://stackoverflow.com/a/655086/1454178)
|
||||||
keyword, though modern C++/C++11 is more complicated with [RAII](https://en.cppreference.com/w/cpp/language/raii).
|
keyword, though modern C++/C++11 is more complicated with [RAII](https://en.cppreference.com/w/cpp/language/raii).
|
||||||
|
|
||||||
For Rust specifically, the principle is this: *stack allocation will be used for everything
|
For Rust specifically, the principle is this: **stack allocation will be used for everything
|
||||||
that doesn't involve "smart pointers" and collections.* If we're interested in dissecting it though,
|
that doesn't involve "smart pointers" and collections.** If we're interested in dissecting it though,
|
||||||
there are three things we pay attention to:
|
there are three things we pay attention to:
|
||||||
|
|
||||||
1. Stack manipulation instructions (`push`, `pop`, and `add`/`sub` of the `rsp` register)
|
1. Stack manipulation instructions (`push`, `pop`, and `add`/`sub` of the `rsp` register)
|
||||||
@ -101,9 +101,7 @@ With all that in mind, let's talk about situations in which we're guaranteed to
|
|||||||
- [`Copy`](https://doc.rust-lang.org/std/marker/trait.Copy.html) types are guaranteed to be
|
- [`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.
|
stack-allocated, and copying them will be done in stack memory.
|
||||||
- [`Iterator`s](https://doc.rust-lang.org/std/iter/trait.Iterator.html) in the standard library
|
- [`Iterator`s](https://doc.rust-lang.org/std/iter/trait.Iterator.html) in the standard library
|
||||||
are stack-allocated. No worrying about some
|
are stack-allocated even when iterating over heap-based collections.
|
||||||
["managed languages"](https://www.youtube.com/watch?v=bSkpMdDe4g4&feature=youtu.be&t=357)
|
|
||||||
creating garbage.
|
|
||||||
|
|
||||||
# Structs
|
# Structs
|
||||||
|
|
||||||
@ -491,3 +489,69 @@ struct NotCopyable {
|
|||||||
|
|
||||||
# Iterators
|
# Iterators
|
||||||
|
|
||||||
|
In [managed memory languages](https://www.youtube.com/watch?v=bSkpMdDe4g4&feature=youtu.be&t=357)
|
||||||
|
(like Java), there's a subtle difference between these two code samples:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public static int sum_for(List<Long> vals) {
|
||||||
|
long sum = 0;
|
||||||
|
// Regular for loop
|
||||||
|
for (int i = 0; i < vals.length; i++) {
|
||||||
|
sum += vals[i];
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int sum_foreach(List<Long> vals) {
|
||||||
|
long sum = 0;
|
||||||
|
// "Foreach" loop - uses iteration
|
||||||
|
for (Long l : vals) {
|
||||||
|
sum += l;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the `sum_for` function, nothing terribly interesting happens. In `sum_foreach`,
|
||||||
|
an object of type [`Iterator`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Iterator.html)
|
||||||
|
is allocated on the heap, and will eventually be garbage-collected. This isn't a great design;
|
||||||
|
iterators are often transient objects that you need during a function and can discard
|
||||||
|
once the function ends. Sounds exactly like the issue stack-allocated objects address, no?
|
||||||
|
|
||||||
|
In Rust, iterators are allocated on the stack. The objects to iterate over are almost
|
||||||
|
certainly in heap memory, but the iterator itself
|
||||||
|
([`Iter`](https://doc.rust-lang.org/std/slice/struct.Iter.html)) doesn't need to use the heap.
|
||||||
|
In each of the examples below we iterate over a collection, but will never need to allocate
|
||||||
|
a object on the heap to clean up:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::collections::HashMap;
|
||||||
|
// There's a lot of assembly generated, but if you search in the text,
|
||||||
|
// there are no references to `real_drop_in_place` anywhere.
|
||||||
|
|
||||||
|
pub fn sum_vec(x: &Vec<u32>) {
|
||||||
|
let mut s = 0;
|
||||||
|
// Basic iteration over vectors doesn't need allocation
|
||||||
|
for y in x {
|
||||||
|
s += y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sum_enumerate(x: &Vec<u32>) {
|
||||||
|
let mut s = 0;
|
||||||
|
// More complex iterators are just fine too
|
||||||
|
for (_i, y) in x.iter().enumerate() {
|
||||||
|
s += y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sum_hm(x: &HashMap<u32, u32>) {
|
||||||
|
let mut s = 0;
|
||||||
|
// And it's not just Vec, all types will allocate the iterator
|
||||||
|
// on stack memory
|
||||||
|
for y in x.values() {
|
||||||
|
s += y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
-- [Compiler Explorer](https://godbolt.org/z/FTT3CT)
|
||||||
|
Loading…
Reference in New Issue
Block a user