Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 37b45d2

Browse files
committedJan 18, 2024
Use a lower threshold for shrinking the ring buffer
Shrink by 50% when the buffer is < 25% occupied. This provides room for growth after shrinking without having to immediately grow the ring buffer. For example, if we were to shrink by 50% when the buffer was < 50% occupied, adding a new item after shrinking would cause the buffer to grow immediately. If the number of items in the deque oscillates around 50% occupancy we would end up with accidentally quadratic behavior; there would be a high chance that each `put` and `get` operation would cause the ring buffer to be resized.
1 parent bc03b85 commit 37b45d2

File tree

1 file changed

+29
-39
lines changed

1 file changed

+29
-39
lines changed
 

‎Modules/_queuemodule.c

+29-39
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,24 @@ RingBuf_Fini(RingBuf *buf)
8585
PyMem_Free(items);
8686
}
8787

88-
static void
89-
coalesce_items(RingBuf *buf, PyObject **new_items, Py_ssize_t new_capacity)
88+
// Resize the underlying items array of buf to the new capacity and arrange
89+
// the items contiguously in the new items array.
90+
//
91+
// Returns -1 on allocation failure or 0 on success.
92+
static int
93+
resize_ringbuf(RingBuf *buf, Py_ssize_t capacity)
9094
{
95+
Py_ssize_t new_capacity = Py_MAX(INITIAL_RING_BUF_CAPACITY, capacity);
96+
if (new_capacity == buf->items_cap) {
97+
return;
98+
}
99+
assert(buf->num_items <= new_capacity);
100+
101+
PyObject **new_items = PyMem_Calloc(new_capacity, sizeof(PyObject *));
102+
if (new_items == NULL) {
103+
return -1;
104+
}
105+
91106
// Copy the "tail" of the old items array. This corresponds to "head" of
92107
// the abstract ring buffer.
93108
Py_ssize_t tail_size = Py_MIN(buf->num_items, buf->items_cap - buf->get_idx);
@@ -107,26 +122,8 @@ coalesce_items(RingBuf *buf, PyObject **new_items, Py_ssize_t new_capacity)
107122
buf->items_cap = new_capacity;
108123
buf->get_idx = 0;
109124
buf->put_idx = buf->num_items;
110-
}
111-
112-
static void
113-
shrink_ringbuf(RingBuf *buf)
114-
{
115-
Py_ssize_t new_capacity =
116-
Py_MAX(INITIAL_RING_BUF_CAPACITY, buf->items_cap / 2);
117-
assert(new_capacity >= buf->num_items);
118-
if (new_capacity == buf->items_cap) {
119-
return;
120-
}
121125

122-
PyObject **new_items = PyMem_Calloc(new_capacity, sizeof(PyObject *));
123-
if (new_items == NULL) {
124-
// It's safe to ignore the failure; shrinking is an optimization and
125-
// isn't required for correctness.
126-
return;
127-
}
128-
129-
coalesce_items(buf, new_items, new_capacity);
126+
return 0;
130127
}
131128

132129
// Returns an owned reference
@@ -135,9 +132,14 @@ RingBuf_Get(RingBuf *buf)
135132
{
136133
assert(buf->num_items > 0);
137134

138-
if (buf->num_items < (buf->items_cap / 2)) {
139-
// Items is less than 50% occupied, shrink it
140-
shrink_ringbuf(buf);
135+
if (buf->num_items < (buf->items_cap / 4)) {
136+
// Items is less than 25% occupied, shrink it by 50%. This allows for
137+
// growth without immediately needing to resize the underlying items
138+
// array.
139+
//
140+
// It's safe it ignore allocation failures here; shrinking is an
141+
// optimization that isn't required for correctness.
142+
resize_ringbuf(buf, buf->items_cap / 2);
141143
}
142144

143145
PyObject *item = buf->items[buf->get_idx];
@@ -147,27 +149,15 @@ RingBuf_Get(RingBuf *buf)
147149
return item;
148150
}
149151

150-
static int
151-
grow_ringbuf(RingBuf *buf)
152-
{
153-
Py_ssize_t new_capacity =
154-
Py_MAX(INITIAL_RING_BUF_CAPACITY, buf->items_cap * 2);
155-
PyObject **new_items = PyMem_Calloc(new_capacity, sizeof(PyObject *));
156-
if (new_items == NULL) {
157-
PyErr_NoMemory();
158-
return -1;
159-
}
160-
coalesce_items(buf, new_items, new_capacity);
161-
return 0;
162-
}
163-
164152
// Returns 0 on success or -1 if the buffer failed to grow.
165153
// Steals a reference to item.
166154
static int
167155
RingBuf_Put(RingBuf *buf, PyObject *item)
168156
{
169157
if (buf->num_items == buf->items_cap) {
170-
if (grow_ringbuf(buf) < 0) {
158+
// Buffer is full, grow it.
159+
if (resize_ringbuf(buf, buf->items_cap * 2) < 0) {
160+
PyErr_NoMemory();
171161
return -1;
172162
}
173163
}

0 commit comments

Comments
 (0)
Please sign in to comment.