Skip to content

Commit 5d332bb

Browse files
committed
Avoid extra usize array by unioning next pointer into elements
This does not yet realize the space savings it promises in the test case due to missing optimizations[1][2] in Rust, but should eventually manage to be barely larger than the payload arrays. [1]: rust-lang/rust#46213 [2]: rust-lang/rust#70477
1 parent 5bea9fc commit 5d332bb

File tree

2 files changed

+66
-20
lines changed

2 files changed

+66
-20
lines changed

src/lib.rs

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -77,24 +77,41 @@ 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+
// Fill back-to-front
110+
// items: GenericArray::generate(|i: usize| if i == 0 { Entry::EmptyLast } else { Entry::EmptyNext(i - 1) }),
111+
// next_free: size.checked_sub(1),
112+
// Fill front-to-back
113+
items: GenericArray::generate(|i: usize| if i == size - 1 { Entry::EmptyLast } else { Entry::EmptyNext(i + 1) }),
114+
next_free: Some(0),
98115
free_count: size
99116
}
100117
}
@@ -108,37 +125,45 @@ impl<IT, N> Slots<IT, N>
108125
}
109126

110127
fn free(&mut self, idx: usize) {
111-
self.free_list[self.free_count] = idx;
128+
self.items[idx] = match self.next_free {
129+
Some(n) => Entry::EmptyNext(n),
130+
None => Entry::EmptyLast
131+
};
132+
self.next_free = Some(idx);
112133
self.free_count += 1;
113134
}
114135

115136
fn alloc(&mut self) -> Option<usize> {
116137
if self.count() == self.capacity() {
117138
None
118139
} else {
119-
let i = self.free_list[self.free_count - 1];
140+
let result = self.next_free;
141+
self.next_free = match self.items[result.expect("Count mismatch")] {
142+
Entry::EmptyNext(n) => Some(n),
143+
Entry::EmptyLast => None,
144+
_ => unreachable!("Non-empty item in entry behind free chain"),
145+
};
120146
self.free_count -= 1;
121-
Some(i)
147+
result
122148
}
123149
}
124150

125151
pub fn store(&mut self, item: IT) -> Result<Key<IT, N>, IT> {
126152
match self.alloc() {
127153
Some(i) => {
128-
self.items[i] = Some(item);
154+
self.items[i] = Entry::Used(item);
129155
Ok(Key::new(i))
130156
}
131157
None => Err(item)
132158
}
133159
}
134160

135161
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!()
162+
let taken = core::mem::replace(&mut self.items[key.index], Entry::EmptyLast);
163+
self.free(key.index);
164+
match taken {
165+
Entry::Used(item) => item,
166+
_ => panic!()
142167
}
143168
}
144169

@@ -151,15 +176,15 @@ impl<IT, N> Slots<IT, N>
151176

152177
pub fn try_read<T, F>(&self, key: usize, function: F) -> Option<T> where F: Fn(&IT) -> T {
153178
match &self.items[key] {
154-
Some(item) => Some(function(&item)),
155-
None => None
179+
Entry::Used(item) => Some(function(&item)),
180+
_ => None
156181
}
157182
}
158183

159184
pub fn modify<T, F>(&mut self, key: &Key<IT, N>, function: F) -> T where F: Fn(&mut IT) -> T {
160185
match self.items[key.index] {
161-
Some(ref mut item) => function(item),
162-
None => panic!()
186+
Entry::Used(ref mut item) => function(item),
187+
_ => panic!()
163188
}
164189
}
165190
}

tests/test.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,24 @@ fn store_returns_err_when_full() {
8383

8484
assert!(k2.is_err());
8585
}
86+
87+
#[test]
88+
/// Verify some size bounds: an N long array over IT is not larger than 3 usize + N * IT (as long
89+
/// as IT is larger than two usize and has two niches)
90+
//
91+
// Failes until https://github.com/rust-lang/rust/issues/46213 is resolved (possibly,
92+
// https://github.com/rust-lang/rust/pull/70477 is sufficient)
93+
fn is_compact() {
94+
struct TwoNichesIn16Byte {
95+
n1: u64,
96+
n2: u32,
97+
n3: u16,
98+
n4: u8,
99+
b: bool,
100+
}
101+
102+
assert_eq!(core::mem::size_of::<TwoNichesIn16Byte>(), 16);
103+
104+
let slots: Slots<TwoNichesIn16Byte, U32> = Slots::new();
105+
assert_eq!(core::mem::size_of::<Slots<TwoNichesIn16Byte, U32>>(), 32 * 16 + 3 * core::mem::size_of::<usize>());
106+
}

0 commit comments

Comments
 (0)