From db16047b69784d8e2f9ad347c4d923c2000953b1 Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Wed, 28 Nov 2018 13:46:28 -0500 Subject: [PATCH] Introduce entry-like vector API in order to reduce interned data copies even more --- webrender/src/intern.rs | 28 +++++++++++----------------- webrender/src/util.rs | 32 +++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/webrender/src/intern.rs b/webrender/src/intern.rs index 9d6ce7ed02..e235b456cb 100644 --- a/webrender/src/intern.rs +++ b/webrender/src/intern.rs @@ -142,16 +142,10 @@ impl DataStore where S: Debug, T: From, M: Debug { for update in update_list.updates { match update.kind { UpdateKind::Insert => { - let data = data_iter.next().unwrap(); - let item = Item { - data: T::from(data), + self.items.entry(update.index).set(Item { + data: T::from(data_iter.next().unwrap()), epoch: update_list.epoch, - }; - if self.items.len() == update.index { - self.items.push(item) - } else { - self.items[update.index] = item; - } + }); } UpdateKind::Remove => { self.items[update.index].epoch = Epoch::INVALID; @@ -161,6 +155,7 @@ impl DataStore where S: Debug, T: From, M: Debug { } } } + debug_assert!(data_iter.next().is_none()); } } @@ -202,6 +197,8 @@ pub struct Interner { update_data: Vec, /// The current epoch for the interner. current_epoch: Epoch, + /// Incrementing counter for identifying stable values. + next_uid: usize, /// The information associated with each interned /// item that can be accessed by the interner. local_data: Vec>, @@ -216,6 +213,7 @@ impl Interner where S: Eq + Hash + Clone + Debug, M: Copy + De updates: Vec::new(), update_data: Vec::new(), current_epoch: Epoch(1), + next_uid: 0, local_data: Vec::new(), } } @@ -271,7 +269,7 @@ impl Interner where S: Eq + Hash + Clone + Debug, M: Copy + De index: index as u32, epoch: self.current_epoch, uid: ItemUid { - uid: self.map.len(), + uid: self.next_uid, _marker: PhantomData, }, _marker: PhantomData, @@ -280,18 +278,14 @@ impl Interner where S: Eq + Hash + Clone + Debug, M: Copy + De // Store this handle so the next time it is // interned, it gets re-used. self.map.insert(data.clone(), handle); + self.next_uid += 1; // Create the local data for this item that is // being interned. - let local_item = Item { + self.local_data.entry(index).set(Item { epoch: self.current_epoch, data: f(), - }; - if self.local_data.len() == index { - self.local_data.push(local_item); - } else { - self.local_data[index] = local_item; - } + }); handle } diff --git a/webrender/src/util.rs b/webrender/src/util.rs index 172f382757..260c7286af 100644 --- a/webrender/src/util.rs +++ b/webrender/src/util.rs @@ -17,7 +17,6 @@ const NEARLY_ZERO: f32 = 1.0 / 4096.0; /// A typesafe helper that separates new value construction from /// vector growing, allowing LLVM to ideally construct the element in place. -#[must_use] pub struct Allocation<'a, T: 'a> { vec: &'a mut Vec, index: usize, @@ -37,8 +36,28 @@ impl<'a, T> Allocation<'a, T> { } } +/// An entry into a vector, similar to `std::collections::hash_map::Entry`. +pub enum VecEntry<'a, T: 'a> { + Vacant(Allocation<'a, T>), + Occupied(&'a mut T), +} + +impl<'a, T> VecEntry<'a, T> { + #[inline(always)] + pub fn set(self, value: T) { + match self { + VecEntry::Vacant(alloc) => { alloc.init(value); } + VecEntry::Occupied(slot) => { *slot = value; } + } + } +} + pub trait VecHelper { + /// Growns the vector by a single entry, returning the allocation. fn alloc(&mut self) -> Allocation; + /// Either returns an existing elemenet, or grows the vector by one. + /// Doesn't expect indices to be higher than the current length. + fn entry(&mut self, index: usize) -> VecEntry; } impl VecHelper for Vec { @@ -52,6 +71,17 @@ impl VecHelper for Vec { index, } } + + fn entry(&mut self, index: usize) -> VecEntry { + if index < self.len() { + VecEntry::Occupied(unsafe { + self.get_unchecked_mut(index) + }) + } else { + assert_eq!(index, self.len()); + VecEntry::Vacant(self.alloc()) + } + } }