/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
///! A custom allocator for memory allocations that have the lifetime of a frame. ///! ///! See also `internal_types::FrameVec`. ///!
use allocator_api2::alloc::{Allocator, AllocError, Layout, Global};
use std::{cell::UnsafeCell, ptr::NonNull, sync::{atomic::{AtomicI32, Ordering}, Arc}};
/// A memory allocator for allocations that have the same lifetime as a built frame. /// /// A custom allocator is used because: /// - The frame is created on a thread and dropped on another thread, which causes /// lock contention in jemalloc. /// - Since all allocations have a very similar lifetime, we can implement much faster /// allocation and deallocation with a specialized allocator than can be achieved /// with a general purpose allocator. /// /// If the allocator is created using `FrameAllocator::fallback()`, it is not /// attached to a `FrameMemory` and simply falls back to the global allocator. This /// should only be used to handle deserialization (for wrench replays) and tests. /// /// # Safety /// /// None of the safety restrictions below apply if the allocator is created using /// `FrameAllocator::fallback`. /// /// `FrameAllocator` can move between thread if and only if it does so along with /// the `FrameMemory` it is associated to (if any). The opposite is also true: it /// is safe to move `FrameMemory` between threads if and only if all live frame /// allocators associated to it move along with it. /// /// `FrameAllocator` must be dropped before the `FrameMemory` it is associated to. /// /// In other words, `FrameAllocator` should only be used for containers that are /// in the `Frame` data structure and not stored elsewhere. The `Frame` holds on /// to its `FrameMemory`, allowing it all to be sent from the frame builder thread /// to the renderer thread together. /// /// Another way to think of it is that the frame is a large self-referential data /// structure, holding on to its memory and a large number of containers that /// point into the memory. pubstruct FrameAllocator { // If this pointer is null, fall back to the global allocator.
inner: *mut FrameInnerAllocator,
impl FrameAllocator { /// Creates a `FrameAllocator` that defaults to the global allocator. /// /// Should only be used for testing purposes or desrialization in wrench replays. pubfn fallback() -> Self {
FrameAllocator {
inner: std::ptr::null_mut(), #[cfg(debug_assertions)]
frame_id: None,
}
}
/// Shorthand for creating a FrameVec. #[inline] pubfn new_vec<T>(self) -> FrameVec<T> {
FrameVec::new_in(self)
}
/// Shorthand for creating a FrameVec. #[inline] pubfn new_vec_with_capacity<T>(self, cap: usize) -> FrameVec<T> {
FrameVec::with_capacity_in(cap, self)
}
impl Clone for FrameAllocator { fn clone(&self) -> Self { unsafe { iflet Some(inner) = self.inner.as_mut() { // When cloning a `FrameAllocator`, we have to decrement the // counter of dropped references in the inner allocator to // balance the fact that an extra `FrameAllocator` will be // dropped (that hasn't been accounted in `FrameMemory`).
inner.references_dropped.fetch_sub(1, Ordering::Relaxed);
}
}
#[cfg(feature = "replay")] impl<'de> serde::Deserialize<'de> for FrameAllocator { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
D: serde::Deserializer<'de>,
{ let _ = <() as serde::Deserialize>::deserialize(deserializer)?;
Ok(FrameAllocator::fallback())
}
}
/// The default impl is required for Deserialize to work in FrameVec. /// It's fine to fallback to the global allocator when replaying wrench /// recording but we don't want to accidentally use `FrameAllocator::default()` /// in regular webrender usage, so we only implement it when the replay /// feature is enabled. #[cfg(feature = "replay")] impl Default for FrameAllocator { fn default() -> Self { Self::fallback()
}
}
/// The backing storage for `FrameAllocator` /// /// This object is meant to be stored in the built frame and must not be dropped or /// recycled before all allocations have been deallocated and all `FrameAllocators` /// have been dropped. In other words, drop or recycle this after dropping the rest /// of the built frame. pubstruct FrameMemory { // Box would be nice but it is not adequate for this purpose because // it is "no-alias". So we do it the hard way and manage this pointer // manually.
/// Safety: The pointed `FrameInnerAllocator` must not move or be deallocated /// while there are live `FrameAllocator`s pointing to it. This is ensured /// by respecting that the `FrameMemory` is dropped last and by the /// `FrameInnerAllocator` not being exposed to the outside world. /// It is also checked at runtime via the reference count.
allocator: Option<NonNull<FrameInnerAllocator>>, /// The number of `FrameAllocator`s created during the current frame. This is /// used to compare aganst the inner allocator's dropped references counter /// to check that references have all been dropped before freeing or recycling /// the memory.
references_created: UnsafeCell<i32>,
}
impl FrameMemory { /// Creates a fallback FrameMemory that uses the global allocator. /// /// This should only be used for testing purposes and to handle the /// deserialization of webrender recordings. #[allow(unused)] pubfn fallback() -> Self {
FrameMemory {
allocator: None,
references_created: UnsafeCell::new(0)
}
}
/// # Panics /// /// A `FrameMemory` must not be dropped until all of the associated /// `FrameAllocators` as well as their allocations have been dropped, /// otherwise the `FrameMemory::drop` will panic. pubfn new(pool: Arc<ChunkPool>, _frame_id: FrameId) -> Self { let layout = Layout::from_size_align(
std::mem::size_of::<FrameInnerAllocator>(),
std::mem::align_of::<FrameInnerAllocator>(),
).unwrap();
/// Shorthand for creating a FrameVec. #[inline] pubfn new_vec<T>(&self) -> FrameVec<T> {
FrameVec::new_in(self.allocator())
}
/// Shorthand for creating a FrameVec. #[inline] pubfn new_vec_with_capacity<T>(&self, cap: usize) -> FrameVec<T> {
FrameVec::with_capacity_in(cap, self.allocator())
}
/// Panics if there are still live allocations or `FrameAllocator`s. pubfn assert_memory_reusable(&self) { iflet Some(ptr) = self.allocator { unsafe { // If this assert blows up, it means an allocation is still alive.
assert_eq!(ptr.as_ref().live_alloc_count.load(Ordering::Acquire), 0); // If this assert blows up, it means one or several FrameAllocators // from the previous frame are still alive. let references_created = *self.references_created.get();
assert_eq!(ptr.as_ref().references_dropped.load(Ordering::Acquire), references_created);
}
}
}
#[cfg(feature = "replay")] impl<'de> serde::Deserialize<'de> for FrameMemory { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
D: serde::Deserializer<'de>,
{ let _ = <() as serde::Deserialize>::deserialize(deserializer)?;
Ok(FrameMemory::fallback())
}
}
struct FrameInnerAllocator {
bump: BumpAllocator,
// Strictly speaking the live allocation and reference count do not need to // be atomic if the allocator is used correctly (the thread that // allocates/deallocates is also the thread where the allocator is). // Since the point of keeping track of the number of live allocations is to // check that the allocator is indeed used correctly, we stay on the safe // side for now.
live_alloc_count: AtomicI32, /// We count the number of references dropped here and compare it against the /// number of references created by the `AllocatorMemory` when we need to check /// that the memory can be safely reused or released. /// This looks and is very similar to a reference counting scheme (`Arc`). The /// main differences are that we don't want the reference count to drive the /// lifetime of the allocator (only to check when we require all references to /// have been dropped), and we do half as many the atomic operations since we only /// count drops and not creations.
references_dropped: AtomicI32, #[cfg(debug_assertions)]
frame_id: Option<FrameId>,
}
#[test] fn frame_memory_simple() { use std::sync::mpsc::channel;
let chunk_pool = Arc::new(ChunkPool::new()); let memory = FrameMemory::new(chunk_pool, FrameId::first());
let alloc = memory.allocator(); let a2 = memory.allocator(); let a3 = memory.allocator(); let v1: FrameVec<u32> = memory.new_vec_with_capacity(10); let v2: FrameVec<u32> = memory.new_vec_with_capacity(256); let v3: FrameVec<u32> = memory.new_vec_with_capacity(1024 * 128); let v4: FrameVec<u32> = memory.new_vec_with_capacity(128); letmut v5 = alloc.clone().new_vec(); for i in0..256u32 {
v5.push(i);
} let v6 = v2.clone().clone().clone().clone(); letmut frame = alloc.new_vec();
frame.push(v1);
frame.push(v2);
frame.push(v3);
frame.push(v4);
frame.push(v5);
frame.push(v6); let (tx, rx) = channel();
tx.send(frame).unwrap();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.