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}