Skip to content

Commit 7b3a2b3

Browse files
authored
Made pool reuse a queue (#612)
So allocator churn will cause remote queues to be visited.
1 parent 365553e commit 7b3a2b3

File tree

3 files changed

+75
-21
lines changed

3 files changed

+75
-21
lines changed

src/snmalloc/mem/pool.h

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ namespace snmalloc
2727
friend class Pool;
2828

2929
private:
30-
MPMCStack<T, PreZeroed> stack;
30+
// Queue of elements in not currently in use
31+
// Must hold lock to modify
32+
capptr::Alloc<T> front{nullptr};
33+
capptr::Alloc<T> back{nullptr};
34+
3135
FlagWord lock{};
3236
capptr::Alloc<T> list{nullptr};
3337

@@ -121,12 +125,20 @@ namespace snmalloc
121125
static T* acquire(Args&&... args)
122126
{
123127
PoolState<T>& pool = get_state();
124-
auto p = capptr::Alloc<T>::unsafe_from(pool.stack.pop());
125-
126-
if (p != nullptr)
127128
{
128-
p->set_in_use();
129-
return p.unsafe_ptr();
129+
FlagLock f(pool.lock);
130+
if (pool.front != nullptr)
131+
{
132+
auto p = pool.front;
133+
auto next = p->next;
134+
if (next == nullptr)
135+
{
136+
pool.back = nullptr;
137+
}
138+
pool.front = next;
139+
p->set_in_use();
140+
return p.unsafe_ptr();
141+
}
130142
}
131143

132144
auto raw =
@@ -137,8 +149,8 @@ namespace snmalloc
137149
Config::Pal::error("Failed to initialise thread local allocator.");
138150
}
139151

140-
p = capptr::Alloc<T>::unsafe_from(new (raw.unsafe_ptr())
141-
T(std::forward<Args>(args)...));
152+
auto p = capptr::Alloc<T>::unsafe_from(new (raw.unsafe_ptr())
153+
T(std::forward<Args>(args)...));
142154

143155
FlagLock f(pool.lock);
144156
p->list_next = pool.list;
@@ -159,16 +171,23 @@ namespace snmalloc
159171
// is returned without the constructor being run, so the object is reused
160172
// without re-initialisation.
161173
p->reset_in_use();
162-
get_state().stack.push(p);
174+
restore(p, p);
163175
}
164176

165177
static T* extract(T* p = nullptr)
166178
{
179+
PoolState<T>& pool = get_state();
167180
// Returns a linked list of all objects in the stack, emptying the stack.
168181
if (p == nullptr)
169-
return get_state().stack.pop_all();
182+
{
183+
FlagLock f(pool.lock);
184+
auto result = pool.front;
185+
pool.front = nullptr;
186+
pool.back = nullptr;
187+
return result.unsafe_ptr();
188+
}
170189

171-
return p->next;
190+
return p->next.unsafe_ptr();
172191
}
173192

174193
/**
@@ -178,9 +197,43 @@ namespace snmalloc
178197
*/
179198
static void restore(T* first, T* last)
180199
{
181-
// Pushes a linked list of objects onto the stack. Use to put a linked
182-
// list returned by extract back onto the stack.
183-
get_state().stack.push(first, last);
200+
PoolState<T>& pool = get_state();
201+
last->next = nullptr;
202+
FlagLock f(pool.lock);
203+
204+
if (pool.front == nullptr)
205+
{
206+
pool.front = capptr::Alloc<T>::unsafe_from(first);
207+
}
208+
else
209+
{
210+
pool.back->next = capptr::Alloc<T>::unsafe_from(first);
211+
}
212+
213+
pool.back = capptr::Alloc<T>::unsafe_from(last);
214+
}
215+
216+
/**
217+
* Return to the pool a list of object previously retrieved by `extract`
218+
*
219+
* Do not return objects from `acquire`.
220+
*/
221+
static void restore_front(T* first, T* last)
222+
{
223+
PoolState<T>& pool = get_state();
224+
last->next = nullptr;
225+
FlagLock f(pool.lock);
226+
227+
if (pool.front == nullptr)
228+
{
229+
pool.back = capptr::Alloc<T>::unsafe_from(last);
230+
}
231+
else
232+
{
233+
last->next = pool.front;
234+
pool.back->next = capptr::Alloc<T>::unsafe_from(first);
235+
}
236+
pool.front = capptr::Alloc<T>::unsafe_from(first);
184237
}
185238

186239
static T* iterate(T* p = nullptr)
@@ -200,7 +253,7 @@ namespace snmalloc
200253
static void sort()
201254
{
202255
// Marker is used to signify free elements.
203-
auto marker = reinterpret_cast<T*>(1);
256+
auto marker = capptr::Alloc<T>::unsafe_from(reinterpret_cast<T*>(1));
204257

205258
// Extract all the elements and mark them as free.
206259
T* curr = extract();
@@ -222,7 +275,7 @@ namespace snmalloc
222275
{
223276
if (curr->next == marker)
224277
{
225-
get_state().stack.push(curr);
278+
restore_front(curr, curr);
226279
}
227280
curr = iterate(curr);
228281
}

src/snmalloc/mem/pooled.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ namespace snmalloc
1717
SNMALLOC_CONCEPT(IsConfig) Config,
1818
PoolState<TT>& get_state()>
1919
friend class Pool;
20-
template<class a, Construction c>
21-
friend class MPMCStack;
2220

2321
/// Used by the pool for chaining together entries when not in use.
24-
std::atomic<T*> next{nullptr};
22+
capptr::Alloc<T> next{nullptr};
2523
/// Used by the pool to keep the list of all entries ever created.
2624
capptr::Alloc<T> list_next;
2725
std::atomic<bool> in_use{false};

src/test/func/pool/pool.cc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ void test_double_alloc()
110110
SNMALLOC_CHECK(ptr1 != ptr2);
111111
PoolA::release(ptr2);
112112
auto ptr3 = PoolA::acquire();
113+
// The following check assumes a stack discipline for acquire/release.
114+
// Placing it first in the list of tests means, there is a single element
115+
// and thus it works for both stack and queue.
113116
SNMALLOC_CHECK(ptr2 == ptr3);
114117
PoolA::release(ptr1);
115118
PoolA::release(ptr3);
@@ -219,14 +222,14 @@ int main(int argc, char** argv)
219222
UNUSED(argc, argv);
220223
#endif
221224

225+
test_double_alloc();
226+
std::cout << "test_double_alloc passed" << std::endl;
222227
test_alloc();
223228
std::cout << "test_alloc passed" << std::endl;
224229
test_constructor();
225230
std::cout << "test_constructor passed" << std::endl;
226231
test_alloc_many();
227232
std::cout << "test_alloc_many passed" << std::endl;
228-
test_double_alloc();
229-
std::cout << "test_double_alloc passed" << std::endl;
230233
test_different_alloc();
231234
std::cout << "test_different_alloc passed" << std::endl;
232235
test_iterator();

0 commit comments

Comments
 (0)