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