Skip to content

Commit 1a9a87c

Browse files
committed
Merge branch 'allocators-2' of https://github.com/sfackler/rfcs
2 parents c1ccd64 + 22fe7cb commit 1a9a87c

File tree

1 file changed

+241
-0
lines changed

1 file changed

+241
-0
lines changed

text/0000-global-allocators.md

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
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

Comments
 (0)