Skip to content

Commit 1619d94

Browse files
author
bors-servo
authored
Auto merge of #3366 - kvark:intern-copy, r=gw3583
Reduce data copies during internation Excessive copying in `fn intern()` is something we noticed yesterday with @jrmuizel . This PR attempts to improve them in two ways (each in a separate commit): 1. reduce the `Update` enum size by moving the data out 2. adopt entry-like API to accommodate the common pattern of `if index == v.len() { v.push(xxx) } else { v[index] = xxx; }` without panics between element construction and actual assignment. It builds upon the `VecHelper` work of #3362. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/webrender/3366) <!-- Reviewable:end -->
2 parents dbaa109 + db16047 commit 1619d94

File tree

2 files changed

+57
-27
lines changed

2 files changed

+57
-27
lines changed

webrender/src/intern.rs

+26-26
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ use internal_types::FastHashMap;
66
use std::fmt::Debug;
77
use std::hash::Hash;
88
use std::marker::PhantomData;
9-
use std::mem;
10-
use std::ops;
11-
use std::u64;
9+
use std::{mem, ops, u64};
10+
use util::VecHelper;
1211

1312
/*
1413
@@ -60,7 +59,9 @@ pub struct UpdateList<S> {
6059
/// The current epoch of the scene builder.
6160
epoch: Epoch,
6261
/// The additions and removals to apply.
63-
updates: Vec<Update<S>>,
62+
updates: Vec<Update>,
63+
/// Actual new data to insert.
64+
data: Vec<S>,
6465
}
6566

6667
#[cfg_attr(feature = "capture", derive(Serialize))]
@@ -89,17 +90,17 @@ impl <T> Handle<T> where T: Copy {
8990

9091
#[cfg_attr(feature = "capture", derive(Serialize))]
9192
#[cfg_attr(feature = "replay", derive(Deserialize))]
92-
pub enum UpdateKind<S> {
93-
Insert(S),
93+
pub enum UpdateKind {
94+
Insert,
9495
Remove,
9596
UpdateEpoch,
9697
}
9798

9899
#[cfg_attr(feature = "capture", derive(Serialize))]
99100
#[cfg_attr(feature = "replay", derive(Deserialize))]
100-
pub struct Update<S> {
101+
pub struct Update {
101102
index: usize,
102-
kind: UpdateKind<S>,
103+
kind: UpdateKind,
103104
}
104105

105106
/// The data item is stored with an epoch, for validating
@@ -137,18 +138,14 @@ impl<S, T, M> DataStore<S, T, M> where S: Debug, T: From<S>, M: Debug {
137138
&mut self,
138139
update_list: UpdateList<S>,
139140
) {
141+
let mut data_iter = update_list.data.into_iter();
140142
for update in update_list.updates {
141143
match update.kind {
142-
UpdateKind::Insert(data) => {
143-
let item = Item {
144-
data: T::from(data),
144+
UpdateKind::Insert => {
145+
self.items.entry(update.index).set(Item {
146+
data: T::from(data_iter.next().unwrap()),
145147
epoch: update_list.epoch,
146-
};
147-
if self.items.len() == update.index {
148-
self.items.push(item)
149-
} else {
150-
self.items[update.index] = item;
151-
}
148+
});
152149
}
153150
UpdateKind::Remove => {
154151
self.items[update.index].epoch = Epoch::INVALID;
@@ -158,6 +155,7 @@ impl<S, T, M> DataStore<S, T, M> where S: Debug, T: From<S>, M: Debug {
158155
}
159156
}
160157
}
158+
debug_assert!(data_iter.next().is_none());
161159
}
162160
}
163161

@@ -194,7 +192,9 @@ pub struct Interner<S : Eq + Hash + Clone + Debug, D, M> {
194192
/// List of free slots in the data store for re-use.
195193
free_list: Vec<usize>,
196194
/// Pending list of updates that need to be applied.
197-
updates: Vec<Update<S>>,
195+
updates: Vec<Update>,
196+
/// Pending new data to insert.
197+
update_data: Vec<S>,
198198
/// The current epoch for the interner.
199199
current_epoch: Epoch,
200200
/// Incrementing counter for identifying stable values.
@@ -211,6 +211,7 @@ impl<S, D, M> Interner<S, D, M> where S: Eq + Hash + Clone + Debug, M: Copy + De
211211
map: FastHashMap::default(),
212212
free_list: Vec::new(),
213213
updates: Vec::new(),
214+
update_data: Vec::new(),
214215
current_epoch: Epoch(1),
215216
next_uid: 0,
216217
local_data: Vec::new(),
@@ -259,8 +260,9 @@ impl<S, D, M> Interner<S, D, M> where S: Eq + Hash + Clone + Debug, M: Copy + De
259260
// Add a pending update to insert the new data.
260261
self.updates.push(Update {
261262
index,
262-
kind: UpdateKind::Insert(data.clone()),
263+
kind: UpdateKind::Insert,
263264
});
265+
self.update_data.alloc().init(data.clone());
264266

265267
// Generate a handle for access via the data store.
266268
let handle = Handle {
@@ -280,15 +282,10 @@ impl<S, D, M> Interner<S, D, M> where S: Eq + Hash + Clone + Debug, M: Copy + De
280282

281283
// Create the local data for this item that is
282284
// being interned.
283-
let local_item = Item {
285+
self.local_data.entry(index).set(Item {
284286
epoch: self.current_epoch,
285287
data: f(),
286-
};
287-
if self.local_data.len() == index {
288-
self.local_data.push(local_item);
289-
} else {
290-
self.local_data[index] = local_item;
291-
}
288+
});
292289

293290
handle
294291
}
@@ -298,6 +295,8 @@ impl<S, D, M> Interner<S, D, M> where S: Eq + Hash + Clone + Debug, M: Copy + De
298295
/// a GC step that removes old entries.
299296
pub fn end_frame_and_get_pending_updates(&mut self) -> UpdateList<S> {
300297
let mut updates = mem::replace(&mut self.updates, Vec::new());
298+
let data = mem::replace(&mut self.update_data, Vec::new());
299+
301300
let free_list = &mut self.free_list;
302301
let current_epoch = self.current_epoch.0;
303302

@@ -327,6 +326,7 @@ impl<S, D, M> Interner<S, D, M> where S: Eq + Hash + Clone + Debug, M: Copy + De
327326

328327
let updates = UpdateList {
329328
updates,
329+
data,
330330
epoch: self.current_epoch,
331331
};
332332

webrender/src/util.rs

+31-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ const NEARLY_ZERO: f32 = 1.0 / 4096.0;
1717

1818
/// A typesafe helper that separates new value construction from
1919
/// vector growing, allowing LLVM to ideally construct the element in place.
20-
#[must_use]
2120
pub struct Allocation<'a, T: 'a> {
2221
vec: &'a mut Vec<T>,
2322
index: usize,
@@ -37,8 +36,28 @@ impl<'a, T> Allocation<'a, T> {
3736
}
3837
}
3938

39+
/// An entry into a vector, similar to `std::collections::hash_map::Entry`.
40+
pub enum VecEntry<'a, T: 'a> {
41+
Vacant(Allocation<'a, T>),
42+
Occupied(&'a mut T),
43+
}
44+
45+
impl<'a, T> VecEntry<'a, T> {
46+
#[inline(always)]
47+
pub fn set(self, value: T) {
48+
match self {
49+
VecEntry::Vacant(alloc) => { alloc.init(value); }
50+
VecEntry::Occupied(slot) => { *slot = value; }
51+
}
52+
}
53+
}
54+
4055
pub trait VecHelper<T> {
56+
/// Growns the vector by a single entry, returning the allocation.
4157
fn alloc(&mut self) -> Allocation<T>;
58+
/// Either returns an existing elemenet, or grows the vector by one.
59+
/// Doesn't expect indices to be higher than the current length.
60+
fn entry(&mut self, index: usize) -> VecEntry<T>;
4261
}
4362

4463
impl<T> VecHelper<T> for Vec<T> {
@@ -52,6 +71,17 @@ impl<T> VecHelper<T> for Vec<T> {
5271
index,
5372
}
5473
}
74+
75+
fn entry(&mut self, index: usize) -> VecEntry<T> {
76+
if index < self.len() {
77+
VecEntry::Occupied(unsafe {
78+
self.get_unchecked_mut(index)
79+
})
80+
} else {
81+
assert_eq!(index, self.len());
82+
VecEntry::Vacant(self.alloc())
83+
}
84+
}
5585
}
5686

5787

0 commit comments

Comments
 (0)