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}