summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Modules/_collectionsmodule.c71
1 files changed, 46 insertions, 25 deletions
diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c
index dc8e698..f7587f1 100644
--- a/Modules/_collectionsmodule.c
+++ b/Modules/_collectionsmodule.c
@@ -23,31 +23,51 @@
#define BLOCKLEN 64
#define CENTER ((BLOCKLEN - 1) / 2)
-/* A `dequeobject` is composed of a doubly-linked list of `block` nodes.
+/* Data for deque objects is stored in a doubly-linked list of fixed
+ * length blocks. This assures that appends or pops never move any
+ * other data elements besides the one being appended or popped.
+ *
+ * Another advantage is that it completely avoids use of realloc(),
+ * resulting in more predictable performance.
+ *
+ * Textbook implementations of doubly-linked lists store one datum
+ * per link, but that gives them a 200% memory overhead (a prev and
+ * next link for each datum) and it costs one malloc() call per data
+ * element. By using fixed-length blocks, the link to data ratio is
+ * significantly improved and there are proportionally fewer calls
+ * to malloc() and free(). The data blocks of consecutive pointers
+ * also improve cache locality.
+ *
* The list of blocks is never empty, so d.leftblock and d.rightblock
* are never equal to NULL. The list is not circular.
*
* A deque d's first element is at d.leftblock[leftindex]
* and its last element is at d.rightblock[rightindex].
- * Unlike Python slice indices, these indices are inclusive
- * on both ends. This makes the algorithms for left and
- * right operations more symmetrical and simplifies the design.
*
- * The indices, d.leftindex and d.rightindex are always in the range
- * 0 <= index < BLOCKLEN.
- * Their exact relationship is:
- * (d.leftindex + d.len - 1) % BLOCKLEN == d.rightindex.
+ * Unlike Python slice indices, these indices are inclusive on both
+ * ends. This makes the algorithms for left and right operations
+ * more symmetrical and it simplifies the design.
*
- * Empty deques have d.len == 0; d.leftblock==d.rightblock;
- * d.leftindex == CENTER+1; and d.rightindex == CENTER.
- * Checking for d.len == 0 is the intended way to see whether d is empty.
+ * The indices, d.leftindex and d.rightindex are always in the range:
+ * 0 <= index < BLOCKLEN
+ *
+ * And their exact relationship is:
+ * (d.leftindex + d.len - 1) % BLOCKLEN == d.rightindex
*
* Whenever d.leftblock == d.rightblock,
- * d.leftindex + d.len - 1 == d.rightindex.
+ * d.leftindex + d.len - 1 == d.rightindex
+ *
+ * However, when d.leftblock != d.rightblock, the d.leftindex and
+ * d.rightindex become indices into distinct blocks and either may
+ * be larger than the other.
*
- * However, when d.leftblock != d.rightblock, d.leftindex and d.rightindex
- * become indices into distinct blocks and either may be larger than the
- * other.
+ * Empty deques have:
+ * d.len == 0
+ * d.leftblock == d.rightblock
+ * d.leftindex == CENTER + 1
+ * d.rightindex == CENTER
+ *
+ * Checking for d.len == 0 is the intended way to see whether d is empty.
*/
typedef struct BLOCK {
@@ -60,8 +80,8 @@ typedef struct {
PyObject_VAR_HEAD
block *leftblock;
block *rightblock;
- Py_ssize_t leftindex; /* in range(BLOCKLEN) */
- Py_ssize_t rightindex; /* in range(BLOCKLEN) */
+ Py_ssize_t leftindex; /* 0 <= leftindex < BLOCKLEN */
+ Py_ssize_t rightindex; /* 0 <= rightindex < BLOCKLEN */
size_t state; /* incremented whenever the indices move */
Py_ssize_t maxlen;
PyObject *weakreflist;
@@ -91,7 +111,7 @@ static PyTypeObject deque_type;
#endif
/* A simple freelisting scheme is used to minimize calls to the memory
- allocator. It accomodates common use cases where new blocks are being
+ allocator. It accommodates common use cases where new blocks are being
added at about the same rate as old blocks are being freed.
*/
@@ -816,6 +836,14 @@ PyDoc_STRVAR(index_doc,
"D.index(value, [start, [stop]]) -> integer -- return first index of value.\n"
"Raises ValueError if the value is not present.");
+/* insert(), remove(), and delitem() are implemented in terms of
+ rotate() for simplicity and reasonable performance near the end
+ points. If for some reason these methods become popular, it is not
+ hard to re-implement this using direct data movement (similar to
+ the code used in list slice assignments) and achieve a performance
+ boost (by moving each pointer only one instead of twice).
+*/
+
static PyObject *
deque_insert(dequeobject *deque, PyObject *args)
{
@@ -945,13 +973,6 @@ deque_item(dequeobject *deque, Py_ssize_t i)
return item;
}
-/* delitem() implemented in terms of rotate for simplicity and reasonable
- performance near the end points. If for some reason this method becomes
- popular, it is not hard to re-implement this using direct data movement
- (similar to code in list slice assignment) and achieve a two or threefold
- performance boost.
-*/
-
static int
deque_del_item(dequeobject *deque, Py_ssize_t i)
{