blink_alloc/
sync.rs

1//! This module provides single-threaded blink allocator.
2
3use core::{
4    alloc::Layout,
5    mem::ManuallyDrop,
6    ptr::NonNull,
7    sync::atomic::{AtomicUsize, Ordering},
8};
9
10#[cfg(feature = "nightly")]
11use core::alloc::{AllocError, Allocator};
12#[cfg(not(feature = "nightly"))]
13use allocator_api2::alloc::{AllocError, Allocator};
14
15#[cfg(all(feature = "nightly", feature = "alloc"))]
16use alloc::alloc::Global;
17
18#[cfg(all(not(feature = "nightly"), feature = "alloc"))]
19use allocator_api2::alloc::Global;
20
21use crate::{
22    api::BlinkAllocator,
23    arena::{ArenaLocal, ArenaSync},
24};
25
26switch_alloc_default! {
27    /// Multi-threaded blink allocator.
28    ///
29    /// Blink-allocator is arena-based allocator that
30    /// allocates memory in growing chunks and serve allocations from them.
31    /// When chunk is exhausted a new larger chunk is allocated.
32    ///
33    /// Deallocation is no-op. [`BlinkAllocator`] can be reset
34    /// to free all chunks except the last one, that will be reused.
35    ///
36    /// Blink allocator aims to allocate a chunk large enough to
37    /// serve all allocations between resets.
38    ///
39    /// A shared and mutable reference to the [`SyncBlinkAlloc`] implement
40    /// [`Allocator`] trait.
41    /// When "nightly" feature is enabled, [`Allocator`] trait is
42    /// [`core::alloc::Allocator`]. Otherwise it is duplicated trait defined
43    /// in [`allocator-api2`](allocator_api2).
44    ///
45    /// Resetting blink allocator requires mutable borrow, so it is not possible
46    /// to do while shared borrow is alive. That matches requirement of
47    /// [`Allocator`] trait - while [`Allocator`] instance
48    /// (a shared reference to `BlinkAlloc`) or any of its clones are alive,
49    /// allocated memory must be valid.
50    ///
51    /// This version of blink-allocator is multi-threaded.
52    /// It can be used from multiple threads concurrently to allocate memory.
53    /// As mutable borrow is required to reset the allocator,
54    /// it is not possible to do when shared.
55    /// Internally it uses [`RwLock`] and [`AtomicUsize`] for synchronized
56    /// interior mutability. [`RwLock`] is only write-locked when new chunk
57    /// must be allocated. The arena allocation is performed using lock-free
58    /// algorithm.
59    ///
60    /// Still it is slower than single-threaded version [`BlinkAlloc`].
61    ///
62    /// For best of both worlds [`LocalBlinkAlloc`] can be created from
63    /// this allocator. [`LocalBlinkAlloc`] will allocate chunks from this
64    /// allocator, but is single-threaded by itself.
65    ///
66    /// [`RwLock`]: parking_lot::RwLock
67    /// [`AtomicUsize`]: core::sync::atomic::AtomicUsize
68    /// [`BlinkAlloc`]: crate::local::BlinkAlloc
69    /// [`LocalBlinkAlloc`]: crate::sync::LocalBlinkAlloc
70    ///
71    /// # Example
72    ///
73    /// ```
74    /// # #![cfg_attr(feature = "nightly", feature(allocator_api))]
75    /// # use blink_alloc::SyncBlinkAlloc;
76    /// # use std::ptr::NonNull;
77    ///
78    /// let mut blink = SyncBlinkAlloc::new();
79    /// let layout = std::alloc::Layout::new::<[u32; 8]>();
80    /// let ptr = blink.allocate(layout).unwrap();
81    /// let ptr = NonNull::new(ptr.as_ptr() as *mut u8).unwrap(); // Method for this is unstable.
82    ///
83    /// unsafe {
84    ///     std::ptr::write(ptr.as_ptr().cast(), [1, 2, 3, 4, 5, 6, 7, 8]);
85    /// }
86    ///
87    /// blink.reset();
88    /// ```
89    ///
90    /// # Example that uses nightly's `allocator_api`
91    ///
92    /// ```
93    /// # #![cfg_attr(feature = "nightly", feature(allocator_api))]
94    /// # use blink_alloc::SyncBlinkAlloc;
95    /// # #[cfg(feature = "nightly")]
96    /// # use std::vec::Vec;
97    /// # #[cfg(not(feature = "nightly"))]
98    /// # use allocator_api2::vec::Vec;
99    /// # fn main() {
100    /// let mut blink = SyncBlinkAlloc::new();
101    /// let mut vec = Vec::new_in(&blink);
102    /// vec.push(1);
103    /// vec.extend(1..3);
104    /// vec.extend(3..10);
105    /// drop(vec);
106    /// blink.reset();
107    /// # }
108    /// ```
109    pub struct SyncBlinkAlloc<A: Allocator = +Global> {
110        arena: ArenaSync,
111        allocator: A,
112        max_local_alloc: AtomicUsize,
113    }
114}
115
116impl<A: Allocator> Drop for SyncBlinkAlloc<A> {
117    fn drop(&mut self) {
118        unsafe {
119            self.arena.reset(false, &self.allocator);
120        }
121    }
122}
123
124#[test]
125fn check_sync() {
126    fn for_sync_alloc<A: Allocator + Sync>() {
127        fn is_sink<T: Sync>() {}
128        is_sink::<SyncBlinkAlloc<A>>();
129    }
130    for_sync_alloc::<Global>();
131}
132
133impl<A> Default for SyncBlinkAlloc<A>
134where
135    A: Allocator + Default,
136{
137    #[inline(always)]
138    fn default() -> Self {
139        Self::new_in(Default::default())
140    }
141}
142
143#[cfg(feature = "alloc")]
144impl SyncBlinkAlloc<Global> {
145    /// Creates new blink allocator that uses global allocator
146    /// to allocate memory chunks.
147    ///
148    /// See [`SyncBlinkAlloc::new_in`] for using custom allocator.
149    #[inline(always)]
150    pub const fn new() -> Self {
151        SyncBlinkAlloc::new_in(Global)
152    }
153}
154
155impl<A> SyncBlinkAlloc<A>
156where
157    A: Allocator,
158{
159    /// Creates new blink allocator that uses provided allocator
160    /// to allocate memory chunks.
161    ///
162    /// See [`SyncBlinkAlloc::new`] for using global allocator.
163    #[inline(always)]
164    pub const fn new_in(allocator: A) -> Self {
165        SyncBlinkAlloc {
166            arena: ArenaSync::new(),
167            allocator,
168            max_local_alloc: AtomicUsize::new(0),
169        }
170    }
171
172    /// Returns reference to the underlying allocator used by this blink allocator.
173    #[inline(always)]
174    pub const fn inner(&self) -> &A {
175        &self.allocator
176    }
177
178    /// Creates new blink allocator that uses global allocator
179    /// to allocate memory chunks.
180    /// With this method you can specify initial chunk size.
181    ///
182    /// See [`SyncBlinkAlloc::new_in`] for using custom allocator.
183    #[inline(always)]
184    pub const fn with_chunk_size_in(chunk_size: usize, allocator: A) -> Self {
185        SyncBlinkAlloc {
186            arena: ArenaSync::with_chunk_size(chunk_size),
187            allocator,
188            max_local_alloc: AtomicUsize::new(0),
189        }
190    }
191
192    /// Creates a new thread-local blink allocator proxy
193    /// that borrows from this multi-threaded allocator.
194    ///
195    /// The local proxy allocator works faster and
196    /// allows more consistent memory reuse.
197    /// It can be recreated without resetting the multi-threaded allocator,
198    /// allowing [`SyncBlinkAlloc`] to be warm-up and serve all allocations
199    /// from a single chunk without ever blocking.
200    ///
201    /// Best works for fork-join style of parallelism.
202    /// Create a local allocator for each thread/task.
203    /// Reset after all threads/tasks are finished.
204    ///
205    /// # Examples
206    ///
207    /// ```
208    /// # #![cfg_attr(feature = "nightly", feature(allocator_api))]
209    /// # use blink_alloc::SyncBlinkAlloc;
210    /// # #[cfg(feature = "nightly")]
211    /// # use std::vec::Vec;
212    /// # #[cfg(not(feature = "nightly"))]
213    /// # use allocator_api2::vec::Vec;
214    /// # #[cfg(feature = "alloc")] fn main() {
215    /// let mut blink = SyncBlinkAlloc::new();
216    /// for _ in 0..3 {
217    ///     for i in 0..16 {
218    ///         std::thread::scope(|_| {
219    ///             let blink = blink.local();
220    ///             let mut vec = Vec::new_in(&blink);
221    ///             vec.push(i);
222    ///             for j in i*2..i*30 {
223    ///                 vec.push(j); // Proxy will allocate enough memory to grow vec without reallocating on 2nd iteration and later.
224    ///             }
225    ///         });
226    ///     }
227    ///     blink.reset();
228    /// }
229    /// # }
230    /// # #[cfg(not(feature = "alloc"))] fn main() {}
231    /// ```
232    #[inline(always)]
233    pub fn local(&self) -> LocalBlinkAlloc<'_, A> {
234        LocalBlinkAlloc {
235            arena: ArenaLocal::with_chunk_size(self.max_local_alloc.load(Ordering::Relaxed)),
236            shared: self,
237        }
238    }
239
240    /// Allocates memory with specified layout from this allocator.
241    /// If needed it will allocate new chunk using underlying allocator.
242    /// If chunk allocation fails, it will return `Err`.
243    #[inline(always)]
244    pub fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
245        // Safety:
246        // Same instance is used for all allocations and resets.
247        if let Some(ptr) = unsafe { self.arena.alloc_fast(layout) } {
248            return Ok(ptr);
249        }
250        unsafe { self.arena.alloc_slow(layout, &self.allocator) }
251    }
252
253    /// Resizes memory allocation.
254    /// Potentially happens in-place.
255    ///
256    /// # Safety
257    ///
258    /// `ptr` must be a pointer previously returned by [`allocate`](SyncBlinkAlloc::allocate).
259    /// `old_size` must be in range `layout.size()..=slice.len()`
260    /// where `layout` is the layout used in the call to [`allocate`](SyncBlinkAlloc::allocate).
261    /// and `slice` is the slice pointer returned by [`allocate`](SyncBlinkAlloc::allocate).
262    ///
263    /// On success, the old pointer is invalidated and the new pointer is returned.
264    /// On error old allocation is still valid.
265    #[inline(always)]
266    pub unsafe fn resize(
267        &self,
268        ptr: NonNull<u8>,
269        old_layout: Layout,
270        new_layout: Layout,
271    ) -> Result<NonNull<[u8]>, AllocError> {
272        if let Some(ptr) = unsafe { self.arena.resize_fast(ptr, old_layout, new_layout) } {
273            return Ok(ptr);
274        }
275
276        // Safety:
277        // Same instance is used for all allocations and resets.
278        // `ptr` was allocated by this allocator.
279        unsafe {
280            self.arena
281                .resize_slow(ptr, old_layout, new_layout, &self.allocator)
282        }
283    }
284
285    /// Deallocates memory previously allocated from this allocator.
286    ///
287    /// This call may not actually free memory.
288    /// All memory is guaranteed to be freed on [`reset`](SyncBlinkAlloc::reset) call.
289    ///
290    /// # Safety
291    ///
292    /// `ptr` must be a pointer previously returned by [`allocate`](SyncBlinkAlloc::allocate).
293    /// `size` must be in range `layout.size()..=slice.len()`
294    /// where `layout` is the layout used in the call to [`allocate`](SyncBlinkAlloc::allocate).
295    /// and `slice` is the slice pointer returned by [`allocate`](SyncBlinkAlloc::allocate).
296    #[inline(always)]
297    pub unsafe fn deallocate(&self, ptr: NonNull<u8>, size: usize) {
298        // Safety:
299        // `ptr` was allocated by this allocator.
300        unsafe {
301            self.arena.dealloc(ptr, size);
302        }
303    }
304
305    /// Resets this allocator, deallocating all chunks except the last one.
306    /// Last chunk will be reused.
307    /// With steady memory usage after few iterations
308    /// one chunk should be sufficient for all allocations between resets.
309    #[inline(always)]
310    pub fn reset(&mut self) {
311        // Safety:
312        // Same instance is used for all allocations and resets.
313        unsafe {
314            self.arena.reset(true, &self.allocator);
315        }
316    }
317
318    /// Resets this allocator, deallocating all chunks.
319    #[inline(always)]
320    pub fn reset_final(&mut self) {
321        // Safety:
322        // Same instance is used for all allocations and resets.
323        unsafe {
324            self.arena.reset(false, &self.allocator);
325        }
326    }
327
328    /// Resets this allocator, deallocating all chunks except the last one.
329    /// Last chunk will be reused.
330    /// With steady memory usage after few iterations
331    /// one chunk should be sufficient for all allocations between resets.
332    ///
333    /// # Safety
334    ///
335    /// Blink-allocators guarantee that memory can be used while shared
336    /// borrow to the allocator is held, preventing safe `fn reset` call.
337    ///
338    /// With this method it becomes caller responsibility to ensure
339    /// that allocated memory won't be used after reset.
340    #[inline(always)]
341    pub unsafe fn reset_unchecked(&self) {
342        // Safety:
343        // Same instance is used for all allocations and resets.
344        unsafe {
345            self.arena.reset_unchecked(true, &self.allocator);
346        }
347    }
348
349    /// Unwrap this allocator, returning the underlying allocator.
350    /// Leaks allocated chunks.
351    ///
352    /// To deallocate all chunks call [`reset_final`](BlinkAlloc::reset_final) first.
353    ///
354    /// The second returned value will use global allocator, so
355    /// use with care if this method is used inside global allocator.
356    pub fn into_inner(self) -> A {
357        let me = ManuallyDrop::new(self);
358        unsafe { core::ptr::read(&me.allocator) }
359    }
360
361    /// Update maximum local allocation size.
362    /// Can be used by thread-local blink-allocators that use
363    /// this shared blink-allocator.
364    #[inline(always)]
365    pub fn update_max_local_alloc(&self, max_local_alloc: usize) {
366        self.max_local_alloc
367            .fetch_max(max_local_alloc, Ordering::Relaxed);
368    }
369}
370
371unsafe impl<A> Allocator for SyncBlinkAlloc<A>
372where
373    A: Allocator,
374{
375    #[inline(always)]
376    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
377        SyncBlinkAlloc::allocate(self, layout)
378    }
379
380    #[inline(always)]
381    unsafe fn shrink(
382        &self,
383        ptr: NonNull<u8>,
384        old_layout: Layout,
385        new_layout: Layout,
386    ) -> Result<NonNull<[u8]>, AllocError> {
387        SyncBlinkAlloc::resize(self, ptr, old_layout, new_layout)
388    }
389
390    #[inline(always)]
391    unsafe fn grow(
392        &self,
393        ptr: NonNull<u8>,
394        old_layout: Layout,
395        new_layout: Layout,
396    ) -> Result<NonNull<[u8]>, AllocError> {
397        SyncBlinkAlloc::resize(self, ptr, old_layout, new_layout)
398    }
399
400    #[inline(always)]
401    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
402        SyncBlinkAlloc::deallocate(self, ptr, layout.size());
403    }
404}
405
406unsafe impl<A> BlinkAllocator for SyncBlinkAlloc<A>
407where
408    A: Allocator,
409{
410    #[inline(always)]
411    fn reset(&mut self) {
412        SyncBlinkAlloc::reset(self)
413    }
414}
415
416switch_alloc_default! {
417    /// Thread-local proxy for [`SyncBlinkAlloc`].
418    ///
419    /// Using proxy can yield better performance when
420    /// it is possible to create proxy once to use for many allocations.
421    ///
422    /// See [`SyncBlinkAlloc::local`] for more details.
423    pub struct LocalBlinkAlloc<'a, A: Allocator = +Global> {
424        arena: ArenaLocal,
425        shared: &'a SyncBlinkAlloc<A>,
426    }
427}
428
429impl<A> Drop for LocalBlinkAlloc<'_, A>
430where
431    A: Allocator,
432{
433    fn drop(&mut self) {
434        self.shared
435            .update_max_local_alloc(self.arena.last_chunk_size());
436        self.arena.reset_leak(false);
437    }
438}
439
440impl<A> LocalBlinkAlloc<'_, A>
441where
442    A: Allocator,
443{
444    /// Allocates memory with specified layout from this allocator.
445    /// If needed it will allocate new chunk using underlying allocator.
446    /// If chunk allocation fails, it will return `Err`.
447    #[inline(always)]
448    pub fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
449        // Safety:
450        // Same instance is used for all allocations and resets.
451        if let Some(ptr) = unsafe { self.arena.alloc_fast(layout) } {
452            return Ok(ptr);
453        }
454        unsafe { self.arena.alloc_slow(layout, self.shared) }
455    }
456
457    /// Resizes memory allocation.
458    /// Potentially happens in-place.
459    ///
460    /// # Safety
461    ///
462    /// `ptr` must be a pointer previously returned by [`allocate`](LocalBlinkAlloc::allocate).
463    /// `old_size` must be in range `layout.size()..=slice.len()`
464    /// where `layout` is the layout used in the call to [`allocate`](LocalBlinkAlloc::allocate).
465    /// and `slice` is the slice pointer returned by [`allocate`](LocalBlinkAlloc::allocate).
466    ///
467    /// On success, the old pointer is invalidated and the new pointer is returned.
468    /// On error old allocation is still valid.
469    #[inline(always)]
470    pub unsafe fn resize(
471        &self,
472        ptr: NonNull<u8>,
473        old_layout: Layout,
474        new_layout: Layout,
475    ) -> Result<NonNull<[u8]>, AllocError> {
476        if let Some(ptr) = unsafe { self.arena.resize_fast(ptr, old_layout, new_layout) } {
477            return Ok(ptr);
478        }
479
480        // Safety:
481        // Same instance is used for all allocations and resets.
482        // `ptr` was allocated by this allocator.
483        unsafe {
484            self.arena
485                .resize_slow(ptr, old_layout, new_layout, self.shared)
486        }
487    }
488
489    /// Deallocates memory previously allocated from this allocator.
490    ///
491    /// This call may not actually free memory.
492    /// All memory is guaranteed to be freed on [`reset`](LocalBlinkAlloc::reset) call.
493    ///
494    /// # Safety
495    ///
496    /// `ptr` must be a pointer previously returned by [`allocate`](LocalBlinkAlloc::allocate).
497    /// `size` must be in range `layout.size()..=slice.len()`
498    /// where `layout` is the layout used in the call to [`allocate`](LocalBlinkAlloc::allocate).
499    /// and `slice` is the slice pointer returned by [`allocate`](LocalBlinkAlloc::allocate).
500    #[inline(always)]
501    pub unsafe fn deallocate(&self, ptr: NonNull<u8>, size: usize) {
502        // Safety:
503        // `ptr` was allocated by this allocator.
504        unsafe {
505            self.arena.dealloc(ptr, size);
506        }
507    }
508
509    /// Resets this allocator, deallocating all chunks except the last one.
510    /// Last chunk will be reused.
511    /// With steady memory usage after few iterations
512    /// one chunk should be sufficient for all allocations between resets.
513    #[inline(always)]
514    pub fn reset(&mut self) {
515        self.shared
516            .update_max_local_alloc(self.arena.last_chunk_size());
517        self.arena.reset_leak(true);
518    }
519
520    /// Resets this allocator, deallocating all chunks except the last one.
521    /// Last chunk will be reused.
522    /// With steady memory usage after few iterations
523    /// one chunk should be sufficient for all allocations between resets.
524    ///
525    /// # Safety
526    ///
527    /// Blink-allocators guarantee that memory can be used while shared
528    /// borrow to the allocator is held, preventing safe `fn reset` call.
529    ///
530    /// With this method it becomes caller responsibility to ensure
531    /// that allocated memory won't be used after reset.
532    #[inline(always)]
533    pub unsafe fn reset_unchecked(&self) {
534        // Safety:
535        // Same instance is used for all allocations and resets.
536        unsafe {
537            self.arena.reset_unchecked(true, self.shared);
538        }
539    }
540}
541
542unsafe impl<A> Allocator for LocalBlinkAlloc<'_, A>
543where
544    A: Allocator,
545{
546    #[inline(always)]
547    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
548        LocalBlinkAlloc::allocate(self, layout)
549    }
550
551    #[inline(always)]
552    unsafe fn shrink(
553        &self,
554        ptr: NonNull<u8>,
555        old_layout: Layout,
556        new_layout: Layout,
557    ) -> Result<NonNull<[u8]>, AllocError> {
558        LocalBlinkAlloc::resize(self, ptr, old_layout, new_layout)
559    }
560
561    #[inline(always)]
562    unsafe fn grow(
563        &self,
564        ptr: NonNull<u8>,
565        old_layout: Layout,
566        new_layout: Layout,
567    ) -> Result<NonNull<[u8]>, AllocError> {
568        LocalBlinkAlloc::resize(self, ptr, old_layout, new_layout)
569    }
570
571    #[inline(always)]
572    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
573        LocalBlinkAlloc::deallocate(self, ptr, layout.size())
574    }
575}
576
577unsafe impl<A> BlinkAllocator for LocalBlinkAlloc<'_, A>
578where
579    A: Allocator,
580{
581    #[inline(always)]
582    fn reset(&mut self) {
583        LocalBlinkAlloc::reset(self)
584    }
585}