blink_alloc/global/sync.rs
1use core::{
2 alloc::{GlobalAlloc, Layout},
3 cell::UnsafeCell,
4 ptr::{null_mut, NonNull},
5};
6
7#[cfg(debug_assertions)]
8use core::sync::atomic::{AtomicU64, Ordering};
9
10#[cfg(feature = "nightly")]
11use core::alloc::{AllocError, Allocator};
12
13#[cfg(not(feature = "nightly"))]
14use allocator_api2::alloc::{AllocError, Allocator};
15
16use crate::{cold, sync::SyncBlinkAlloc, LocalBlinkAlloc};
17
18struct State<A: Allocator> {
19 blink: SyncBlinkAlloc<A>,
20 enabled: bool,
21}
22
23impl<A: Allocator> State<A> {
24 #[inline(always)]
25 fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
26 match self.enabled {
27 true => self.blink.allocate(layout),
28 false => {
29 cold();
30 self.blink.inner().allocate(layout)
31 }
32 }
33 }
34
35 #[inline(always)]
36 fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
37 match self.enabled {
38 true => self.blink.allocate_zeroed(layout),
39 false => {
40 cold();
41 self.blink.inner().allocate_zeroed(layout)
42 }
43 }
44 }
45
46 #[inline(always)]
47 unsafe fn resize(
48 &self,
49 ptr: NonNull<u8>,
50 old_layout: Layout,
51 new_layout: Layout,
52 ) -> Result<NonNull<[u8]>, AllocError> {
53 match self.enabled {
54 true => self.blink.resize(ptr, old_layout, new_layout),
55 false => {
56 cold();
57 if old_layout.size() >= new_layout.size() {
58 self.blink.inner().grow(ptr, old_layout, new_layout)
59 } else {
60 self.blink.inner().shrink(ptr, old_layout, new_layout)
61 }
62 }
63 }
64 }
65
66 #[inline(always)]
67 unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
68 match self.enabled {
69 true => self.blink.deallocate(ptr, layout.size()),
70 false => {
71 cold();
72 self.blink.inner().deallocate(ptr, layout)
73 }
74 }
75 }
76}
77
78switch_std_default! {
79 /// [`GlobalAlloc`] implementation based on [`SyncBlinkAlloc`].
80 ///
81 /// # Example
82 ///
83 /// ```
84 /// use blink_alloc::GlobalBlinkAlloc;
85 ///
86 /// #[global_allocator]
87 /// static GLOBAL_ALLOC: GlobalBlinkAlloc = GlobalBlinkAlloc::new();
88 ///
89 /// fn main() {
90 /// let _ = Box::new(42);
91 /// let _ = vec![1, 2, 3];
92 /// }
93 /// ```
94 pub struct GlobalBlinkAlloc<A: Allocator = +std::alloc::System> {
95 state: UnsafeCell<State<A>>,
96 #[cfg(debug_assertions)]
97 allocations: AtomicU64,
98 }
99}
100
101unsafe impl<A: Allocator + Send> Send for GlobalBlinkAlloc<A> {}
102unsafe impl<A: Allocator + Sync> Sync for GlobalBlinkAlloc<A> {}
103
104#[cfg(feature = "std")]
105impl GlobalBlinkAlloc<std::alloc::System> {
106 /// Create a new [`GlobalBlinkAlloc`].
107 ///
108 /// Const function can be used to initialize a static variable.
109 ///
110 /// # Example
111 ///
112 /// ```
113 /// use blink_alloc::GlobalBlinkAlloc;
114 ///
115 /// #[global_allocator]
116 /// static GLOBAL_ALLOC: GlobalBlinkAlloc = GlobalBlinkAlloc::new();
117 ///
118 /// fn main() {
119 /// let _ = Box::new(42);
120 /// let _ = vec![1, 2, 3];
121 /// }
122 /// ```
123 pub const fn new() -> Self {
124 GlobalBlinkAlloc::new_in(std::alloc::System)
125 }
126
127 /// Create a new [`GlobalBlinkAlloc`].
128 ///
129 /// This method allows to specify initial chunk size.
130 ///
131 /// Const function can be used to initialize a static variable.
132 ///
133 /// # Example
134 ///
135 /// ```
136 /// use blink_alloc::GlobalBlinkAlloc;
137 ///
138 /// #[global_allocator]
139 /// static GLOBAL_ALLOC: GlobalBlinkAlloc = GlobalBlinkAlloc::new();
140 ///
141 /// fn main() {
142 /// let _ = Box::new(42);
143 /// let _ = vec![1, 2, 3];
144 /// }
145 /// ```
146 pub const fn with_chunk_size(chunk_size: usize) -> Self {
147 GlobalBlinkAlloc::with_chunk_size_in(chunk_size, std::alloc::System)
148 }
149}
150
151impl<A> GlobalBlinkAlloc<A>
152where
153 A: Allocator,
154{
155 /// Create a new [`GlobalBlinkAlloc`]
156 /// with specified underlying allocator.
157 ///
158 /// Const function can be used to initialize a static variable.
159 ///
160 /// # Example
161 ///
162 /// ```
163 /// use blink_alloc::GlobalBlinkAlloc;
164 ///
165 /// #[global_allocator]
166 /// static GLOBAL_ALLOC: GlobalBlinkAlloc<std::alloc::System> = GlobalBlinkAlloc::new_in(std::alloc::System);
167 ///
168 /// fn main() {
169 /// let _ = Box::new(42);
170 /// let _ = vec![1, 2, 3];
171 /// }
172 /// ```
173 pub const fn new_in(allocator: A) -> Self {
174 GlobalBlinkAlloc {
175 state: UnsafeCell::new(State {
176 blink: SyncBlinkAlloc::new_in(allocator),
177 enabled: false,
178 }),
179 #[cfg(debug_assertions)]
180 allocations: AtomicU64::new(0),
181 }
182 }
183
184 /// Create a new [`GlobalBlinkAlloc`]
185 /// with specified underlying allocator.
186 ///
187 /// This method allows to specify initial chunk size.
188 ///
189 /// Const function can be used to initialize a static variable.
190 ///
191 /// # Example
192 ///
193 /// ```
194 /// use blink_alloc::GlobalBlinkAlloc;
195 ///
196 /// #[global_allocator]
197 /// static GLOBAL_ALLOC: GlobalBlinkAlloc<std::alloc::System> = GlobalBlinkAlloc::new_in(std::alloc::System);
198 ///
199 /// fn main() {
200 /// let _ = Box::new(42);
201 /// let _ = vec![1, 2, 3];
202 /// }
203 /// ```
204 pub const fn with_chunk_size_in(chunk_size: usize, allocator: A) -> Self {
205 GlobalBlinkAlloc {
206 state: UnsafeCell::new(State {
207 blink: SyncBlinkAlloc::with_chunk_size_in(chunk_size, allocator),
208 enabled: false,
209 }),
210 #[cfg(debug_assertions)]
211 allocations: AtomicU64::new(0),
212 }
213 }
214
215 /// Resets this allocator, deallocating all chunks except the last one.
216 /// Last chunk will be reused.
217 /// With steady memory usage after few iterations
218 /// one chunk should be sufficient for all allocations between resets.
219 ///
220 /// # Safety
221 ///
222 /// Memory allocated from this allocator in blink mode becomes invalidated.
223 /// The user is responsible to ensure that previously allocated memory
224 /// won't be used after reset.
225 ///
226 /// # Example
227 ///
228 /// ```
229 /// # #[cfg(feature = "std")] fn main() {
230 /// use blink_alloc::UnsafeGlobalBlinkAlloc;
231 ///
232 /// #[global_allocator]
233 /// static GLOBAL_ALLOC: UnsafeGlobalBlinkAlloc = unsafe { UnsafeGlobalBlinkAlloc::new() };
234 ///
235 /// unsafe { GLOBAL_ALLOC.blink_mode() };
236 ///
237 /// let b = Box::new(42);
238 /// let v = vec![1, 2, 3];
239 /// drop(b);
240 /// drop(v);
241 ///
242 /// // Safety: memory allocated in blink mode won't be used after reset.
243 /// unsafe {
244 /// GLOBAL_ALLOC.reset();
245 /// GLOBAL_ALLOC.direct_mode();
246 /// };
247 /// # }
248 /// # #[cfg(not(feature = "std"))] fn main() {}
249 /// ```
250 #[inline(always)]
251 pub unsafe fn reset(&self) {
252 #[cfg(debug_assertions)]
253 {
254 assert_eq!(
255 self.allocations.load(Ordering::SeqCst),
256 0,
257 "Not everything was deallocated"
258 );
259 }
260
261 (*self.state.get()).blink.reset_unchecked();
262 }
263
264 /// Switches allocator to blink mode.
265 /// All allocations will be served by blink-allocator.
266 ///
267 /// The type is created in direct mode.
268 /// When used as global allocator, user may manually switch into blink mode
269 /// in `main` or at any point later.
270 ///
271 /// However user must switch back to direct mode before returning from `main`.
272 ///
273 /// # Safety
274 ///
275 /// Must be externally synchronized with other threads accessing this allocator.
276 /// Memory allocated in direct mode must not be deallocated while in blink mode.
277 #[inline(always)]
278 pub unsafe fn blink_mode(&self) {
279 (*self.state.get()).enabled = true;
280 }
281
282 /// Switches allocator to direct mode.
283 /// All allocations will be served by underlying allocator.
284 ///
285 /// The type is created in direct mode.
286 /// When used as global allocator, user may manually switch into blink mode
287 /// in `main` or at any point later.
288 ///
289 /// However user must switch back to direct mode before returning from `main`.
290 ///
291 /// # Safety
292 ///
293 /// Must be externally synchronized with other threads accessing this allocator.
294 /// Memory allocated in blink mode must not be deallocated while in direct mode.
295 #[inline(always)]
296 pub unsafe fn direct_mode(&self) {
297 self.reset();
298 (*self.state.get()).enabled = false;
299 }
300
301 /// Creates a new thread-local blink allocator proxy
302 /// that borrows from this multi-threaded allocator.
303 ///
304 /// The local proxy allocator works faster and
305 /// allows more consistent memory reuse.
306 /// It can be recreated without resetting the multi-threaded allocator,
307 /// allowing [`SyncBlinkAlloc`] to be warm-up and serve all allocations
308 /// from a single chunk without ever blocking.
309 ///
310 /// Best works for fork-join style of parallelism.
311 /// Create a local allocator for each thread/task.
312 /// Reset after all threads/tasks are finished.
313 ///
314 /// # Examples
315 ///
316 /// ```
317 /// # #![cfg_attr(feature = "nightly", feature(allocator_api))]
318 /// # use blink_alloc::GlobalBlinkAlloc;
319 /// # #[cfg(feature = "nightly")]
320 /// # use std::vec::Vec;
321 /// # #[cfg(not(feature = "nightly"))]
322 /// # use allocator_api2::vec::Vec;
323 /// # fn main() {
324 /// static BLINK: GlobalBlinkAlloc = GlobalBlinkAlloc::new();
325 ///
326 /// for _ in 0..3 {
327 /// for i in 0..16 {
328 /// let mut blink = BLINK.local(); // Sendable and 'static.
329 /// std::thread::scope(move |_| {
330 /// let mut vec = Vec::new_in(&blink);
331 /// vec.push(i);
332 /// for j in i*2..i*30 {
333 /// vec.push(j); // Proxy will allocate enough memory to grow vec without reallocating on 2nd iteration and later.
334 /// }
335 /// drop(vec); // Without this line it will fail to borrow mutable on next line.
336 /// blink.reset();
337 /// });
338 ///
339 /// // Safety: Proxies and allocations are dropped.
340 /// unsafe { BLINK.reset() };
341 /// }
342 /// }
343 /// # }
344 /// ```
345 pub fn local(&self) -> LocalBlinkAlloc<'_, A> {
346 unsafe { (*self.state.get()).blink.local() }
347 }
348}
349
350unsafe impl<A> GlobalAlloc for GlobalBlinkAlloc<A>
351where
352 A: Allocator,
353{
354 #[inline]
355 unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 {
356 match (*self.state.get()).allocate(layout) {
357 Ok(ptr) => {
358 #[cfg(debug_assertions)]
359 if (*self.state.get()).enabled {
360 self.allocations.fetch_add(1, Ordering::SeqCst);
361 }
362 ptr.as_ptr().cast()
363 }
364 Err(_) => null_mut(),
365 }
366 }
367
368 #[inline]
369 unsafe fn dealloc(&self, ptr: *mut u8, layout: core::alloc::Layout) {
370 let ptr = NonNull::new_unchecked(ptr);
371 (*self.state.get()).deallocate(ptr, layout);
372 #[cfg(debug_assertions)]
373 {
374 if (*self.state.get()).enabled {
375 let _ = self.allocations.fetch_sub(1, Ordering::SeqCst);
376 }
377 }
378 }
379
380 #[inline]
381 unsafe fn alloc_zeroed(&self, layout: core::alloc::Layout) -> *mut u8 {
382 match (*self.state.get()).allocate_zeroed(layout) {
383 Ok(ptr) => {
384 #[cfg(debug_assertions)]
385 if (*self.state.get()).enabled {
386 self.allocations.fetch_add(1, Ordering::SeqCst);
387 }
388 ptr.as_ptr().cast()
389 }
390 Err(_) => null_mut(),
391 }
392 }
393
394 #[inline]
395 unsafe fn realloc(
396 &self,
397 ptr: *mut u8,
398 layout: core::alloc::Layout,
399 new_size: usize,
400 ) -> *mut u8 {
401 let Ok(new_layout) = Layout::from_size_align(new_size, layout.align()) else {
402 return null_mut();
403 };
404
405 let result = match NonNull::new(ptr) {
406 None => (*self.state.get()).allocate(new_layout),
407 Some(ptr) => (*self.state.get()).resize(ptr, layout, new_layout),
408 };
409
410 match result {
411 Ok(ptr) => ptr.as_ptr().cast(),
412 Err(_) => null_mut(),
413 }
414 }
415}