Skip to content

Commit 3c9a967

Browse files
authored
Merge pull request #1 from chrysn-pull-requests/no-extra-usize-array
Avoid extra usize array by unioning next pointer into elements
2 parents 0ce14d1 + 653e2a7 commit 3c9a967

2 files changed

Lines changed: 96 additions & 28 deletions

File tree

src/lib.rs

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -77,24 +77,37 @@ impl<IT, N> Key<IT, N> {
7777
}
7878
}
7979

80+
/// Module to hide the Entry type which needs to be public due to the generic-array internals.
81+
mod entry {
82+
pub enum Entry<IT> {
83+
Used(IT),
84+
EmptyNext(usize),
85+
EmptyLast
86+
}
87+
}
88+
89+
use entry::Entry;
90+
8091
// Data type that stores values and returns a key that can be used to manipulate
8192
// the stored values.
8293
// Values can be read by anyone but can only be modified using the key.
8394
pub struct Slots<IT, N>
84-
where N: ArrayLength<Option<IT>> + ArrayLength<usize> + Unsigned {
85-
items: GenericArray<Option<IT>, N>,
86-
free_list: GenericArray<usize, N>,
95+
where N: ArrayLength<Entry<IT>> + Unsigned {
96+
items: GenericArray<Entry<IT>, N>,
97+
// Could be optimized by making it just usize and relying on free_count to determine its
98+
// validity
99+
next_free: Option<usize>,
87100
free_count: usize
88101
}
89102

90103
impl<IT, N> Slots<IT, N>
91-
where N: ArrayLength<Option<IT>> + ArrayLength<usize> + Unsigned {
104+
where N: ArrayLength<Entry<IT>> + Unsigned {
92105
pub fn new() -> Self {
93106
let size = N::to_usize();
94107

95108
Self {
96-
items: GenericArray::default(),
97-
free_list: GenericArray::generate(|i: usize| size - i - 1),
109+
items: GenericArray::generate(|i| i.checked_sub(1).map(Entry::EmptyNext).unwrap_or(Entry::EmptyLast)),
110+
next_free: size.checked_sub(1),
98111
free_count: size
99112
}
100113
}
@@ -108,37 +121,41 @@ impl<IT, N> Slots<IT, N>
108121
}
109122

110123
fn free(&mut self, idx: usize) {
111-
self.free_list[self.free_count] = idx;
124+
self.items[idx] = match self.next_free {
125+
Some(n) => Entry::EmptyNext(n),
126+
None => Entry::EmptyLast
127+
};
128+
self.next_free = Some(idx);
112129
self.free_count += 1;
113130
}
114131

115132
fn alloc(&mut self) -> Option<usize> {
116-
if self.count() == self.capacity() {
117-
None
118-
} else {
119-
let i = self.free_list[self.free_count - 1];
120-
self.free_count -= 1;
121-
Some(i)
122-
}
133+
let index = self.next_free?;
134+
self.next_free = match self.items[index] {
135+
Entry::EmptyNext(n) => Some(n),
136+
Entry::EmptyLast => None,
137+
_ => unreachable!("Non-empty item in entry behind free chain"),
138+
};
139+
self.free_count -= 1;
140+
Some(index)
123141
}
124142

125143
pub fn store(&mut self, item: IT) -> Result<Key<IT, N>, IT> {
126144
match self.alloc() {
127145
Some(i) => {
128-
self.items[i] = Some(item);
146+
self.items[i] = Entry::Used(item);
129147
Ok(Key::new(i))
130148
}
131149
None => Err(item)
132150
}
133151
}
134152

135153
pub fn take(&mut self, key: Key<IT, N>) -> IT {
136-
match self.items[key.index].take() {
137-
Some(item) => {
138-
self.free(key.index);
139-
item
140-
}
141-
None => panic!()
154+
let taken = core::mem::replace(&mut self.items[key.index], Entry::EmptyLast);
155+
self.free(key.index);
156+
match taken {
157+
Entry::Used(item) => item,
158+
_ => panic!()
142159
}
143160
}
144161

@@ -151,15 +168,15 @@ impl<IT, N> Slots<IT, N>
151168

152169
pub fn try_read<T, F>(&self, key: usize, function: F) -> Option<T> where F: FnOnce(&IT) -> T {
153170
match &self.items[key] {
154-
Some(item) => Some(function(&item)),
155-
None => None
171+
Entry::Used(item) => Some(function(&item)),
172+
_ => None
156173
}
157174
}
158175

159176
pub fn modify<T, F>(&mut self, key: &Key<IT, N>, function: F) -> T where F: FnOnce(&mut IT) -> T {
160177
match self.items[key.index] {
161-
Some(ref mut item) => function(item),
162-
None => panic!()
178+
Entry::Used(ref mut item) => function(item),
179+
_ => panic!()
163180
}
164181
}
165182
}

tests/test.rs

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ fn index_can_be_used_to_read_value() {
3333
slots.store(6).unwrap();
3434
slots.store(7).unwrap();
3535

36-
assert_eq!(5, slots.try_read(0, |&w| w).unwrap());
37-
assert_eq!(6, slots.try_read(1, |&w| w).unwrap());
38-
assert_eq!(7, slots.try_read(2, |&w| w).unwrap());
36+
assert_eq!(5, slots.try_read(7, |&w| w).unwrap());
37+
assert_eq!(6, slots.try_read(6, |&w| w).unwrap());
38+
assert_eq!(7, slots.try_read(5, |&w| w).unwrap());
3939
}
4040

4141
#[test]
@@ -83,3 +83,54 @@ fn store_returns_err_when_full() {
8383

8484
assert!(k2.is_err());
8585
}
86+
87+
#[should_panic(expected = "assertion failed: `(left == right)`\n left: `792`,\n right: `536`")]
88+
#[test]
89+
/// Verify some size bounds: an N long array over IT is not larger than 3 usize + N * IT (as long
90+
/// as IT is larger than two usize and has two niches)
91+
//
92+
// Fails until https://github.com/rust-lang/rust/issues/46213 is resolved (possibly,
93+
// https://github.com/rust-lang/rust/pull/70477 is sufficient). When this starts not failing any
94+
// more, be happy, remove the panic, and figure out how to skip the test on older Rust versions.
95+
// (If left just goes down but does not reach right, that should be investigated further, as it
96+
// indicates that the optimization was implemented incompletely, or it turns out it is not possible
97+
// for some reasons and needs fixing in the code).
98+
fn is_compact() {
99+
#[allow(unused)]
100+
struct TwoNichesIn16Byte {
101+
n1: u64,
102+
n2: u32,
103+
n3: u16,
104+
n4: u8,
105+
b: bool,
106+
}
107+
108+
assert_eq!(core::mem::size_of::<TwoNichesIn16Byte>(), 16);
109+
110+
assert_eq!(core::mem::size_of::<Slots<TwoNichesIn16Byte, U32>>(), 32 * 16 + 3 * core::mem::size_of::<usize>());
111+
}
112+
113+
#[test]
114+
fn capacity_and_count() {
115+
let mut slots: Slots<u8, U4> = Slots::new();
116+
117+
assert_eq!(slots.capacity(), 4);
118+
assert_eq!(slots.count(), 0);
119+
120+
let k1 = slots.store(1).unwrap();
121+
let k2 = slots.store(2).unwrap();
122+
123+
assert_eq!(slots.count(), 2);
124+
125+
let k3 = slots.store(3).unwrap();
126+
let k4 = slots.store(4).unwrap();
127+
128+
assert_eq!(slots.count(), 4);
129+
130+
slots.take(k1);
131+
slots.take(k2);
132+
slots.take(k3);
133+
slots.take(k4);
134+
135+
assert_eq!(slots.count(), 0);
136+
}

0 commit comments

Comments
 (0)