mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 16:48:10 -05:00
Finish post on QADAPT
This commit is contained in:
parent
1386d91da0
commit
cb2fa7dc64
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
layout: post
|
layout: post
|
||||||
title: "QADAPT - Allocation Safety in Rust"
|
title: "QADAPT - debug_assert! for your memory usage"
|
||||||
description: "...and why you want an allocator that goes 💥."
|
description: "...and why you want an allocator that goes 💥."
|
||||||
category:
|
category:
|
||||||
tags: []
|
tags: []
|
||||||
@ -21,27 +21,30 @@ There's another part of the human condition that derives joy from seeing things
|
|||||||
|
|
||||||
<iframe src="https://giphy.com/embed/YA6dmVW0gfIw8" width="480" height="336" frameBorder="0"></iframe>
|
<iframe src="https://giphy.com/embed/YA6dmVW0gfIw8" width="480" height="336" frameBorder="0"></iframe>
|
||||||
|
|
||||||
And *that's* the part of the human condition I'm going to focus on.
|
And *that's* the part I'm going to focus on.
|
||||||
|
|
||||||
# Why an Allocator
|
# Why an Allocator?
|
||||||
|
|
||||||
So why, after complaining about allocators, would I want to go back and write one myself?
|
So why, after complaining about allocators, would I still want to write one?
|
||||||
There are two reasons for that:
|
There are three reasons for that:
|
||||||
|
|
||||||
1. **Allocation/dropping is slow**
|
1. Allocation/dropping is slow
|
||||||
2. **It's difficult to know when exactly Rust will allocate/drop, especially when using
|
2. It's difficult to know exactly when Rust will allocate or drop, especially when using
|
||||||
code that you did not write**
|
code that you did not write
|
||||||
|
3. I want automated tools to verify behavior, instead of inspecting by hand
|
||||||
|
|
||||||
When I say "slow," it's important to define the terms. If you're writing web applications,
|
When I say "slow," it's important to define the terms. If you're writing web applications,
|
||||||
you'll spend orders of magnitude more time waiting for the database than you will the allocator.
|
you'll spend orders of magnitude more time waiting for the database than you will the allocator.
|
||||||
However, there's still plenty of code where micro- or nano-seconds matter; think finance,
|
However, there's still plenty of code where micro- or nano-seconds matter; think
|
||||||
|
[finance](https://www.youtube.com/watch?v=NH1Tta7purM),
|
||||||
[real-time audio](https://www.reddit.com/r/rust/comments/9hg7yj/synthesizer_progress_update/e6c291f),
|
[real-time audio](https://www.reddit.com/r/rust/comments/9hg7yj/synthesizer_progress_update/e6c291f),
|
||||||
[self-driving cars](https://polysync.io/blog/session-types-for-hearty-codecs/), and networking.
|
[self-driving cars](https://polysync.io/blog/session-types-for-hearty-codecs/), and
|
||||||
|
[networking](https://carllerche.github.io/bytes/bytes/index.html).
|
||||||
In these situations it's simply unacceptable for you to spend time doing things
|
In these situations it's simply unacceptable for you to spend time doing things
|
||||||
that are not your program, and waiting on the allocator takes a great deal of time.
|
that are not your program, and waiting on the allocator is not cool.
|
||||||
|
|
||||||
Secondly, it can be difficult to predict where exactly allocations will happen in Rust code. We're going
|
As I continue to learn Rust, it's difficult for me to predict where exactly allocations will happen.
|
||||||
to play a quick trivia game: **Does this code trigger an allocation?**
|
So, I propose we play a quick trivia game: **Does this code invoke the allocator?**
|
||||||
|
|
||||||
## Example 1
|
## Example 1
|
||||||
|
|
||||||
@ -53,8 +56,8 @@ fn my_function() {
|
|||||||
|
|
||||||
**No**: Rust [knows how big](https://doc.rust-lang.org/std/mem/fn.size_of.html)
|
**No**: Rust [knows how big](https://doc.rust-lang.org/std/mem/fn.size_of.html)
|
||||||
the `Vec` type is, and reserves a fixed amount of memory on the stack for the `v` vector.
|
the `Vec` type is, and reserves a fixed amount of memory on the stack for the `v` vector.
|
||||||
If we were to reserve extra space (using `Vec::with_capacity`), this would trigger
|
However, if we wanted to reserve extra space (using `Vec::with_capacity`) the allocator
|
||||||
an allocation.
|
would get invoked.
|
||||||
|
|
||||||
## Example 2
|
## Example 2
|
||||||
|
|
||||||
@ -65,8 +68,9 @@ fn my_function() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Yes**: Because Boxes allow us to work with things that are of unknown size, it has to allocate
|
**Yes**: Because Boxes allow us to work with things that are of unknown size, it has to allocate
|
||||||
on the heap even though the vector has a known size at compile time. Some release builds may
|
on the heap. While the `Box` is unnecessary in this snippet (release builds will optimize out
|
||||||
optimize out the Box in this specific example, but it's not guaranteed to happen.
|
the allocation), reserving heap space more generally is needed to pass a dynamically sized type
|
||||||
|
to another function.
|
||||||
|
|
||||||
## Example 3
|
## Example 3
|
||||||
|
|
||||||
@ -77,25 +81,25 @@ fn my_function(v: Vec<u8>) {
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Maybe**: Depending on whether the Vector we were given has space available, we may or may not allocate.
|
**Maybe**: Depending on whether the Vector we were given has space available, we may or may not allocate.
|
||||||
Especially when dealing with code that you did not author, it's helpful to have a system double-check
|
Especially when dealing with code that you did not author, it's difficult to verify that things behave
|
||||||
that you didn't accidentally introduce an allocation or drop somewhere unintended.
|
as you expect them to.
|
||||||
|
|
||||||
# Blowing Things Up
|
# Blowing Things Up
|
||||||
|
|
||||||
So, how exactly does QADAPT solve these problems? **Whenever an allocation/drop occurs in code marked
|
So, how exactly does QADAPT solve these problems? **Whenever an allocation or drop occurs in code marked
|
||||||
allocation-safe, QADAPT triggers a thread panic.** We don't want to let the program continue as if
|
allocation-safe, QADAPT triggers a thread panic.** We don't want to let the program continue as if
|
||||||
nothing strange happened, *we want things to explode*.
|
nothing strange happened, *we want things to explode*.
|
||||||
|
|
||||||
However, you don't want code to panic in production because of circumstances you didn't predict.
|
However, you don't want code to panic in production because of circumstances you didn't predict.
|
||||||
Just like [`debug_assert!`](https://doc.rust-lang.org/std/macro.debug_assert.html),
|
Just like [`debug_assert!`](https://doc.rust-lang.org/std/macro.debug_assert.html),
|
||||||
QADAPT will strip out its own code when building in release mode to guarantee no panics and
|
**QADAPT will strip out its own code when building in release mode to guarantee no panics and
|
||||||
no performance impact.
|
no performance impact.**
|
||||||
|
|
||||||
Finally, there are three ways to have QADAPT check that your code is allocation-free:
|
Finally, there are three ways to have QADAPT check that your code will not invoke the allocator:
|
||||||
|
|
||||||
## Using a procedural macro
|
## Using a procedural macro
|
||||||
|
|
||||||
Easiest method, marks an entire function as not allocating/drop safe:
|
The easiest method, watch an entire function for allocator invocation:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use qadapt::no_alloc;
|
use qadapt::no_alloc;
|
||||||
@ -139,17 +143,17 @@ fn main() {
|
|||||||
assert_no_alloc!(v.push(5));
|
assert_no_alloc!(v.push(5));
|
||||||
|
|
||||||
// Even though we remove an item, it doesn't trigger a drop
|
// Even though we remove an item, it doesn't trigger a drop
|
||||||
// because it's a scalar
|
// because it's a scalar. If it were a `Box<_>` type,
|
||||||
|
// a drop would trigger.
|
||||||
assert_no_alloc!({
|
assert_no_alloc!({
|
||||||
let mut x = v.pop().unwrap();
|
v.pop().unwrap();
|
||||||
x += 1;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Using function calls
|
## Using function calls
|
||||||
|
|
||||||
Both the most precise and most tedious method:
|
Both the most precise and most tedious:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use qadapt::enter_protected;
|
use qadapt::enter_protected;
|
||||||
@ -206,9 +210,10 @@ fn main() {
|
|||||||
|
|
||||||
While there's a lot more to writing high-performance code than managing your usage
|
While there's a lot more to writing high-performance code than managing your usage
|
||||||
of the allocator, it's critical that you do use the allocator correctly.
|
of the allocator, it's critical that you do use the allocator correctly.
|
||||||
QADAPT is here to verify that your code is doing what you expect.
|
QADAPT will verify that your code is doing what you expect. It's usable even on
|
||||||
|
stable Rust from version 1.31 onward, which isn't the case for most allocators.
|
||||||
|
|
||||||
I'll be writing more about high-performance code in Rust in the future, and I expect
|
I'm hoping to write more about high-performance Rust in the future, and I expect
|
||||||
that QADAPT will help guide that. If there are topics you're interested in,
|
that QADAPT will help guide that. If there are topics you're interested in,
|
||||||
let me know in the comments below!
|
let me know in the comments below!
|
||||||
|
|
Loading…
Reference in New Issue
Block a user