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}