|
| 1 | +- Feature Name: `allocator` |
| 2 | +- Start Date: 2017-02-04 |
| 3 | +- RFC PR: |
| 4 | +- Rust Issue: |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +Overhaul the global allocator APIs to put them on a path to stabilization, and |
| 10 | +switch the default allocator to the system allocator when the feature |
| 11 | +stabilizes. |
| 12 | + |
| 13 | +This RFC is a refinement of the previous [RFC 1183][]. |
| 14 | + |
| 15 | +[RFC 1183]: https://github.com/rust-lang/rfcs/blob/master/text/1183-swap-out-jemalloc.md |
| 16 | + |
| 17 | +# Motivation |
| 18 | +[motivation]: #motivation |
| 19 | + |
| 20 | +## The current API |
| 21 | + |
| 22 | +The unstable `allocator` feature allows developers to select the global |
| 23 | +allocator which will be used in a program. A crate identifies itself as an |
| 24 | +allocator with the `#![allocator]` annotation, and declares a number of |
| 25 | +allocation functions with specific `#[no_mangle]` names and a C ABI. To |
| 26 | +override the default global allocator, a crate simply pulls an allocator in |
| 27 | +via an `extern crate`. |
| 28 | + |
| 29 | +There are a couple of issues with the current approach: |
| 30 | + |
| 31 | +A C-style ABI is error prone - nothing ensures that the signatures are correct, |
| 32 | +and if a function is omitted that error will be caught by the linker rather than |
| 33 | +compiler. |
| 34 | + |
| 35 | +Allocators have some state, and with the current API, that state is forced to be |
| 36 | +truly global since bare functions can't carry state. |
| 37 | + |
| 38 | +Since an allocator is automatically selected when it is pulled into the crate |
| 39 | +graph, it is painful to compose allocators. For example, one may want to create |
| 40 | +an allocator which records statistics about active allocations, or adds padding |
| 41 | +around allocations to attempt to detect buffer overflows in unsafe code. To do |
| 42 | +this currently, the underlying allocator would need to be split into two |
| 43 | +crates, one which contains all of the functionality and another which is tagged |
| 44 | +as an `#![allocator]`. |
| 45 | + |
| 46 | +## jemalloc |
| 47 | + |
| 48 | +Rust's default allocator has historically been jemalloc. While jemalloc does |
| 49 | +provide significant speedups over certain system allocators for some allocation |
| 50 | +heavy workflows, it has has been a source of problems. For example, it has |
| 51 | +deadlock issues on Windows, does not work with Valgrind, adds ~300KB to |
| 52 | +binaries, and has caused crashes on macOS 10.12. See [this comment][] for more |
| 53 | +details. As a result, it is already disabled on many targets, including all of |
| 54 | +Windows. While there are certainly contexts in which jemalloc is a good choice, |
| 55 | +developers should be making that decision, not the compiler. The system |
| 56 | +allocator is a more reasonable and unsurprising default choice. |
| 57 | + |
| 58 | +A third party crate allowing users to opt-into jemalloc would also open the door |
| 59 | +to provide access to some of the library's other features such as tracing, arena |
| 60 | +pinning, and diagnostic output dumps for code that depends on jemalloc directly. |
| 61 | + |
| 62 | +[this comment]: https://github.com/rust-lang/rust/issues/36963#issuecomment-252029017 |
| 63 | + |
| 64 | +# Detailed design |
| 65 | +[design]: #detailed-design |
| 66 | + |
| 67 | +## Defining an allocator |
| 68 | + |
| 69 | +Global allocators will use the `Allocator` trait defined in [RFC 1398][]. |
| 70 | +However `Allocator`'s methods take `&mut self` since it's designed to be used |
| 71 | +with individual collections. Since this allocator is global across threads, we |
| 72 | +can't take `&mut self` references to it. So, instead of implementing `Allocator` |
| 73 | +for the allocator type itself, it is implemented for shared references to the |
| 74 | +allocator. This is a bit strange, but similar to `File`'s `Read` and `Write` |
| 75 | +implementations, for example. |
| 76 | + |
| 77 | +```rust |
| 78 | +pub struct Jemalloc; |
| 79 | + |
| 80 | +impl<'a> Allocator for &'a Jemalloc { |
| 81 | + // ... |
| 82 | +} |
| 83 | +``` |
| 84 | + |
| 85 | +[RFC 1398]: https://github.com/rust-lang/rfcs/blob/master/text/1398-kinds-of-allocators.md |
| 86 | + |
| 87 | +## Using an allocator |
| 88 | + |
| 89 | +The `alloc::heap` module will contain several items: |
| 90 | + |
| 91 | +```rust |
| 92 | +/// Defined in RFC 1398 |
| 93 | +pub struct Layout { ... } |
| 94 | + |
| 95 | +/// Defined in RFC 1398 |
| 96 | +pub unsafe trait Allocator { ... } |
| 97 | + |
| 98 | +/// An `Allocator` which uses the system allocator. |
| 99 | +/// |
| 100 | +/// This uses `malloc`/`free` on Unix systems, and `HeapAlloc`/`HeapFree` on |
| 101 | +/// Windows, for example. |
| 102 | +pub struct System; |
| 103 | + |
| 104 | +unsafe impl Allocator for System { ... } |
| 105 | + |
| 106 | +unsafe impl<'a> Allocator for &'a System { ... } |
| 107 | + |
| 108 | +/// An `Allocator` which uses the configured global allocator. |
| 109 | +/// |
| 110 | +/// The global allocator is selected by defining a static instance of the |
| 111 | +/// allocator and annotating it with `#[global_allocator]`. Only one global |
| 112 | +/// allocator can be defined in a crate graph. |
| 113 | +/// |
| 114 | +/// # Note |
| 115 | +/// |
| 116 | +/// For techical reasons, only non-generic methods of the `Allocator` trait |
| 117 | +/// will be forwarded to the selected global allocator in the current |
| 118 | +/// implementation. |
| 119 | +pub struct Heap; |
| 120 | + |
| 121 | +unsafe impl Allocator for Heap { ... } |
| 122 | + |
| 123 | +unsafe impl<'a> Allocator for &'a Heap { ... } |
| 124 | +``` |
| 125 | + |
| 126 | +This module will be reexported as `std::alloc`, which will be the location at |
| 127 | +which it will be stabilized. The `alloc` crate is not proposed for stabilization |
| 128 | +at this time. |
| 129 | + |
| 130 | +An example of setting the global allocator: |
| 131 | + |
| 132 | +```rust |
| 133 | +extern crate my_allocator; |
| 134 | + |
| 135 | +use my_allocator::{MyAllocator, MY_ALLOCATOR_INIT}; |
| 136 | + |
| 137 | +#[global_allocator] |
| 138 | +static ALLOCATOR: MyAllocator = MY_ALLOCATOR_INIT; |
| 139 | + |
| 140 | +fn main() { |
| 141 | + ... |
| 142 | +} |
| 143 | +``` |
| 144 | + |
| 145 | +Note that `ALLOCATOR` is still a normal static value - it can be used like any |
| 146 | +other static would be. |
| 147 | + |
| 148 | +The existing `alloc_system` and `alloc_jemalloc` crates will likely be |
| 149 | +deprecated and eventually removed. The `alloc_system` crate is replaced with the |
| 150 | +`SystemAllocator` structure in the standard library and the `alloc_jemalloc` |
| 151 | +crate will become available on crates.io. The `alloc_jemalloc` crate will likely |
| 152 | +look like: |
| 153 | + |
| 154 | +```rust |
| 155 | +pub struct Jemalloc; |
| 156 | + |
| 157 | +unsafe impl Allocator for Jemalloc { |
| 158 | + // ... |
| 159 | +} |
| 160 | + |
| 161 | +unsafe impl<'a> Allocator for &'a Jemalloc { |
| 162 | + // ... |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | +It is not proposed in this RFC to switch the per-platform default allocator just |
| 167 | +yet. Assuming everything goes smoothly, however, it will likely be defined as |
| 168 | +`System` as platforms transition away from jemalloc-by-default once the |
| 169 | +jemalloc-from-crates.io is stable and usable. |
| 170 | + |
| 171 | +The compiler will also no longer forbid cyclic the cyclic dependency between a |
| 172 | +crate defining an implementation of an allocator and the `alloc` crate itself. |
| 173 | +As a vestige of the current implementation this is only to get around linkage |
| 174 | +errors where the liballoc rlib references symbols defined in the "allocator |
| 175 | +crate". With this RFC the compiler has far more control over the ABI and linkage |
| 176 | +here, so this restriction is no longer necessary. |
| 177 | + |
| 178 | +# How We Teach This |
| 179 | +[how-we-teach-this]: #how-we-teach-this |
| 180 | + |
| 181 | +Global allocator selection would be a somewhat advanced topic - the system |
| 182 | +allocator is sufficient for most use cases. It is a new tool that developers can |
| 183 | +use to optimize for their program's specific workload when necessary. |
| 184 | + |
| 185 | +It should be emphasized that in most cases, the "terminal" crate (i.e. the bin, |
| 186 | +cdylib or staticlib crate) should be the only thing selecting the global |
| 187 | +allocator. Libraries should be agnostic over the global allocator unless they |
| 188 | +are specifically designed to augment functionality of a specific allocator. |
| 189 | + |
| 190 | +Defining an allocator is an even more advanced topic that should probably live |
| 191 | +in the _Nomicon_. |
| 192 | + |
| 193 | +[RFC 1398]: https://github.com/rust-lang/rfcs/pull/1398 |
| 194 | + |
| 195 | +# Drawbacks |
| 196 | +[drawbacks]: #drawbacks |
| 197 | + |
| 198 | +Dropping the default of jemalloc will regress performance of some programs until |
| 199 | +they manually opt back into that allocator, which may produce confusion in the |
| 200 | +community as to why things suddenly became slower. |
| 201 | + |
| 202 | +Depending on implementation of a trait for references to a type is unfortunate. |
| 203 | +It's pretty strange and unfamiliar to many Rust developers. Many global |
| 204 | +allocators are zero-sized as their state lives outside of the Rust structure, |
| 205 | +but a reference to the allocator will be 4 or 8 bytes. If developers wish to use |
| 206 | +global allocators as "normal" allocators in individual collections, allocator |
| 207 | +authors may have to implement `Allocator` twice - for the type and references to |
| 208 | +the type. One can forward to the other, but it's still work that would not need |
| 209 | +to be done ideally. |
| 210 | + |
| 211 | +In theory, there could be a blanket implementation of `impl<'a, T> Allocator for |
| 212 | +T where &'a T: Allocator`, but the compiler is unfortunately not able to deal |
| 213 | +with this currently. |
| 214 | + |
| 215 | +The `Allocator` trait defines some functions which have generic arguments. |
| 216 | +They're purely convenience functions, but if a global allocator overrides them, |
| 217 | +the custom implementations will not be used when going through the `Heap` type. |
| 218 | +This may be confusing. |
| 219 | + |
| 220 | +# Alternatives |
| 221 | +[alternatives]: #alternatives |
| 222 | + |
| 223 | +We could define a separate `GlobalAllocator` trait with methods taking `&self` |
| 224 | +to avoid the strange implementation for references requirement. This does |
| 225 | +require the duplication of some or all of the API surface and documentation of |
| 226 | +`Allocator` to a second trait with only a difference in receiver type. |
| 227 | + |
| 228 | +The `GlobalAllocator` trait could be responsible for simply returning a type |
| 229 | +which implements `Allocator`. This avoids the duplication or the strange |
| 230 | +implementation for references issues in the other possibilities, but can't be |
| 231 | +defined in a reasonable way without HKT, and is a somewhat strange layer of |
| 232 | +indirection. |
| 233 | + |
| 234 | +# Unresolved questions |
| 235 | +[unresolved]: #unresolved-questions |
| 236 | + |
| 237 | +Are `System` and `Heap` the right names for the two `Allocator` implementations |
| 238 | +in `std::heap`? |
| 239 | + |
| 240 | +Should `std::heap` also have free functions which forward to the global |
| 241 | +allocator? |
0 commit comments