Commit abdd1414 authored by Matt Stancliff's avatar Matt Stancliff
Browse files

Allow compression of interior quicklist nodes

Let user set how many nodes to *not* compress.

We can specify a compression "depth" of how many nodes
to leave uncompressed on each end of the quicklist.

Depth 0 = disable compression.
Depth 1 = only leave head/tail uncompressed.
  - (read as: "skip 1 node on each end of the list before compressing")
Depth 2 = leave head, head->next, tail->prev, tail uncompressed.
  - ("skip 2 nodes on each end of the list before compressing")
Depth 3 = Depth 2 + head->next->next + tail->prev->prev
  - ("skip 3 nodes...")
etc.

This also:
  - updates RDB storage to use native quicklist compression (if node is
    already compressed) instead of uncompressing, generating the RDB string,
    then re-compressing the quicklist node.
  - internalizes the "fill" parameter for the quicklist so we don't
    need to pass it to _every_ function.  Now it's just a property of
    the list.
  - allows a runtime-configurable compression option, so we can
    expose a compresion parameter in the configuration file if people
    want to trade slight request-per-second performance for up to 90%+
    memory savings in some situations.
  - updates the quicklist tests to do multiple passes: 200k+ tests now.
parent 5127e399
...@@ -307,7 +307,7 @@ void debugCommand(redisClient *c) { ...@@ -307,7 +307,7 @@ void debugCommand(redisClient *c) {
int remaining = sizeof(extra); int remaining = sizeof(extra);
quicklist *ql = val->ptr; quicklist *ql = val->ptr;
double avg = (double)ql->count/ql->len; double avg = (double)ql->count/ql->len;
int used = snprintf(nextra, remaining, " ql_nodes:%lu", ql->len); int used = snprintf(nextra, remaining, " ql_nodes:%u", ql->len);
nextra += used; nextra += used;
remaining -= used; remaining -= used;
snprintf(nextra, remaining, " ql_avg_node:%.2f", avg); snprintf(nextra, remaining, " ql_avg_node:%.2f", avg);
......
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
#include "zmalloc.h" #include "zmalloc.h"
#include "ziplist.h" #include "ziplist.h"
#include "util.h" /* for ll2string */ #include "util.h" /* for ll2string */
#include "lzf.h"
#if defined(REDIS_TEST) || defined(REDIS_TEST_VERBOSE) #if defined(REDIS_TEST) || defined(REDIS_TEST_VERBOSE)
#include <stdio.h> /* for printf (debug printing), snprintf (genstr) */ #include <stdio.h> /* for printf (debug printing), snprintf (genstr) */
...@@ -41,8 +42,18 @@ ...@@ -41,8 +42,18 @@
/* Optimization levels for size-based filling */ /* Optimization levels for size-based filling */
static const size_t optimization_level[] = { 4096, 8192, 16384, 32768, 65536 }; static const size_t optimization_level[] = { 4096, 8192, 16384, 32768, 65536 };
/* Maximum size in bytes of any multi-element ziplist.
* Larger values will live in their own isolated ziplists. */
#define SIZE_SAFETY_LIMIT 8192 #define SIZE_SAFETY_LIMIT 8192
/* Minimum ziplist size in bytes for attempting compression. */
#define MIN_COMPRESS_BYTES 48
/* Minimum size reduction in bytes to store compressed quicklistNode data.
* This also prevents us from storing compression if the compression
* resulted in a larger size than the original data. */
#define MIN_COMPRESS_IMPROVE 8
/* If not verbose testing, remove all debug printing. */ /* If not verbose testing, remove all debug printing. */
#ifndef REDIS_TEST_VERBOSE #ifndef REDIS_TEST_VERBOSE
#define D(...) #define D(...)
...@@ -75,6 +86,40 @@ quicklist *quicklistCreate(void) { ...@@ -75,6 +86,40 @@ quicklist *quicklistCreate(void) {
quicklist->head = quicklist->tail = NULL; quicklist->head = quicklist->tail = NULL;
quicklist->len = 0; quicklist->len = 0;
quicklist->count = 0; quicklist->count = 0;
quicklist->compress = 0;
quicklist->fill = -2;
return quicklist;
}
#define COMPRESS_MAX (1 << 16)
void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
if (compress > COMPRESS_MAX) {
compress = COMPRESS_MAX;
} else if (compress < 0) {
compress = 0;
}
quicklist->compress = compress;
}
#define FILL_MAX (1 << 15)
void quicklistSetFill(quicklist *quicklist, int fill) {
if (fill > FILL_MAX) {
fill = FILL_MAX;
} else if (fill < -5) {
fill = -5;
}
quicklist->fill = fill;
}
void quicklistSetOptions(quicklist *quicklist, int fill, int depth) {
quicklistSetFill(quicklist, fill);
quicklistSetCompressDepth(quicklist, depth);
}
/* Create a new quicklist with some default parameters. */
quicklist *quicklistNew(int fill, int compress) {
quicklist *quicklist = quicklistCreate();
quicklistSetOptions(quicklist, fill, compress);
return quicklist; return quicklist;
} }
...@@ -85,6 +130,9 @@ static quicklistNode *quicklistCreateNode(void) { ...@@ -85,6 +130,9 @@ static quicklistNode *quicklistCreateNode(void) {
node->count = 0; node->count = 0;
node->sz = 0; node->sz = 0;
node->next = node->prev = NULL; node->next = node->prev = NULL;
node->encoding = QUICKLIST_NODE_ENCODING_RAW;
node->container = QUICKLIST_NODE_CONTAINER_ZIPLIST;
node->recompress = 0;
return node; return node;
} }
...@@ -112,8 +160,183 @@ void quicklistRelease(quicklist *quicklist) { ...@@ -112,8 +160,183 @@ void quicklistRelease(quicklist *quicklist) {
zfree(quicklist); zfree(quicklist);
} }
/* Compress the ziplist in 'node' and update encoding details.
* Returns 1 if ziplist compressed successfully.
* Returns 0 if compression failed or if ziplist too small to compress. */
static int __quicklistCompressNode(quicklistNode *node) {
#ifdef REDIS_TEST
node->attempted_compress = 1;
#endif
/* Don't bother compressing small values */
if (node->sz < MIN_COMPRESS_BYTES)
return 0;
quicklistLZF *lzf = zmalloc(sizeof(*lzf) + node->sz);
/* Cancel if compression fails or doesn't compress small enough */
if (((lzf->sz = lzf_compress(node->zl, node->sz, lzf->compressed,
node->sz)) == 0) ||
lzf->sz + MIN_COMPRESS_IMPROVE >= node->sz) {
/* lzf_compress aborts/rejects compression if value not compressable. */
zfree(lzf);
return 0;
}
lzf = zrealloc(lzf, sizeof(*lzf) + lzf->sz);
zfree(node->zl);
node->zl = (unsigned char *)lzf;
node->encoding = QUICKLIST_NODE_ENCODING_LZF;
node->recompress = 0;
return 1;
}
/* Compress only uncompressed nodes. */
#define quicklistCompressNode(_node) \
do { \
if ((_node) && (_node)->encoding == QUICKLIST_NODE_ENCODING_RAW) { \
__quicklistCompressNode((_node)); \
} \
} while (0)
/* Uncompress the ziplist in 'node' and update encoding details.
* Returns 1 on successful decode, 0 on failure to decode. */
static int __quicklistDecompressNode(quicklistNode *node) {
#ifdef REDIS_TEST
node->attempted_compress = 0;
#endif
void *decompressed = zmalloc(node->sz);
quicklistLZF *lzf = (quicklistLZF *)node->zl;
if (lzf_decompress(lzf->compressed, lzf->sz, decompressed, node->sz) == 0) {
/* Someone requested decompress, but we can't decompress. Not good. */
zfree(decompressed);
return 0;
}
zfree(lzf);
node->zl = decompressed;
node->encoding = QUICKLIST_NODE_ENCODING_RAW;
return 1;
}
/* Decompress only compressed nodes. */
#define quicklistDecompressNode(_node) \
do { \
if ((_node) && (_node)->encoding == QUICKLIST_NODE_ENCODING_LZF) { \
__quicklistDecompressNode((_node)); \
} \
} while (0)
/* Force node to not be immediately re-compresable */
#define quicklistDecompressNodeForUse(_node) \
do { \
if ((_node) && (_node)->encoding == QUICKLIST_NODE_ENCODING_LZF) { \
__quicklistDecompressNode((_node)); \
(_node)->recompress = 1; \
} \
} while (0)
/* Extract the raw LZF data from this quicklistNode.
* Pointer to LZF data is assigned to '*data'.
* Return value is the length of compressed LZF data. */
size_t quicklistGetLzf(const quicklistNode *node, void **data) {
quicklistLZF *lzf = (quicklistLZF *)node->zl;
*data = lzf->compressed;
return lzf->sz;
}
#define quicklistAllowsCompression(_ql) ((_ql)->compress != 0)
/* Force 'quicklist' to meet compression guidelines set by compress depth.
* The only way to guarantee interior nodes get compressed is to iterate
* to our "interior" compress depth then compress the next node we find.
* If compress depth is larger than the entire list, we return immediately. */
static void __quicklistCompress(const quicklist *quicklist,
quicklistNode *node) {
/* If length is less than our compress depth (from both sides),
* we can't compress anything. */
if (!quicklistAllowsCompression(quicklist) ||
quicklist->len < (unsigned int)(quicklist->compress * 2))
return;
#if 0
/* Optimized cases for small depth counts */
if (quicklist->compress == 1) {
quicklistNode *h = quicklist->head, *t = quicklist->tail;
quicklistDecompressNode(h);
quicklistDecompressNode(t);
if (h != node && t != node)
quicklistCompressNode(node);
return;
} else if (quicklist->compress == 2) {
quicklistNode *h = quicklist->head, *hn = h->next, *hnn = hn->next;
quicklistNode *t = quicklist->tail, *tp = t->prev, *tpp = tp->prev;
quicklistDecompressNode(h);
quicklistDecompressNode(hn);
quicklistDecompressNode(t);
quicklistDecompressNode(tp);
if (h != node && hn != node && t != node && tp != node) {
quicklistCompressNode(node);
}
if (hnn != t) {
quicklistCompressNode(hnn);
}
if (tpp != h) {
quicklistCompressNode(tpp);
}
return;
}
#endif
/* Iterate until we reach compress depth for both sides of the list.a
* Note: because we do length checks at the *top* of this function,
* we can skip explicit null checks below. Everything exists. */
quicklistNode *forward = quicklist->head;
quicklistNode *reverse = quicklist->tail;
int depth = 0;
int in_depth = 0;
while (depth++ < quicklist->compress) {
quicklistDecompressNode(forward);
quicklistDecompressNode(reverse);
if (forward == node || reverse == node)
in_depth = 1;
if (forward == reverse)
return;
forward = forward->next;
reverse = reverse->prev;
}
if (!in_depth)
quicklistCompressNode(node);
if (depth > 2) {
/* At this point, forward and reverse are one node beyond depth */
quicklistCompressNode(forward);
quicklistCompressNode(reverse);
}
}
#define quicklistCompress(_ql, _node) \
do { \
if ((_node)->recompress) \
quicklistCompressNode((_node)); \
else \
__quicklistCompress((_ql), (_node)); \
} while (0)
/* If we previously used quicklistDecompressNodeForUse(), just recompress. */
#define quicklistRecompressOnly(_ql, _node) \
do { \
if ((_node)->recompress) \
quicklistCompressNode((_node)); \
} while (0)
/* Insert 'new_node' after 'old_node' if 'after' is 1. /* Insert 'new_node' after 'old_node' if 'after' is 1.
* Insert 'new_node' before 'old_node' if 'after' is 0. */ * Insert 'new_node' before 'old_node' if 'after' is 0.
* Note: 'new_node' is *always* uncompressed, so if we assign it to
* head or tail, we do not need to uncompress it. */
static void __quicklistInsertNode(quicklist *quicklist, quicklistNode *old_node, static void __quicklistInsertNode(quicklist *quicklist, quicklistNode *old_node,
quicklistNode *new_node, int after) { quicklistNode *new_node, int after) {
if (after) { if (after) {
...@@ -137,11 +360,14 @@ static void __quicklistInsertNode(quicklist *quicklist, quicklistNode *old_node, ...@@ -137,11 +360,14 @@ static void __quicklistInsertNode(quicklist *quicklist, quicklistNode *old_node,
if (quicklist->head == old_node) if (quicklist->head == old_node)
quicklist->head = new_node; quicklist->head = new_node;
} }
/* If this insert creates the first element in this quicklist, we /* If this insert creates the only element so far, initialize head/tail. */
* need to initialize head/tail too. */
if (quicklist->len == 0) { if (quicklist->len == 0) {
quicklist->head = quicklist->tail = new_node; quicklist->head = quicklist->tail = new_node;
} }
if (old_node)
quicklistCompress(quicklist, old_node);
quicklist->len++; quicklist->len++;
} }
...@@ -232,44 +458,48 @@ static int _quicklistNodeAllowMerge(const quicklistNode *a, ...@@ -232,44 +458,48 @@ static int _quicklistNodeAllowMerge(const quicklistNode *a,
(node)->sz = ziplistBlobLen((node)->zl); \ (node)->sz = ziplistBlobLen((node)->zl); \
} while (0) } while (0)
/* Add new entry to head of quicklist. /* Add new entry to head node of quicklist.
* *
* Returns 'quicklist' argument. */ * Returns 0 if used existing head.
quicklist *quicklistPushHead(quicklist *quicklist, const int fill, void *value, * Returns 1 if new head created. */
size_t sz) { int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
if (_quicklistNodeAllowInsert(quicklist->head, fill, sz)) { quicklistNode *orig_head = quicklist->head;
if (_quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz)) {
quicklist->head->zl = quicklist->head->zl =
ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD); ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);
quicklistNodeUpdateSz(quicklist->head);
} else { } else {
quicklistNode *node = quicklistCreateNode(); quicklistNode *node = quicklistCreateNode();
node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD); node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
quicklistNodeUpdateSz(node);
_quicklistInsertNodeBefore(quicklist, quicklist->head, node); _quicklistInsertNodeBefore(quicklist, quicklist->head, node);
} }
quicklist->count++; quicklist->count++;
quicklist->head->count++; quicklist->head->count++;
quicklistNodeUpdateSz(quicklist->head); return (orig_head != quicklist->head);
return quicklist;
} }
/* Add new node to tail of quicklist. /* Add new entry to tail node of quicklist.
* *
* Returns 'quicklist' argument. */ * Returns 0 if used existing tail.
quicklist *quicklistPushTail(quicklist *quicklist, const int fill, void *value, * Returns 1 if new tail created. */
size_t sz) { int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {
if (_quicklistNodeAllowInsert(quicklist->tail, fill, sz)) { quicklistNode *orig_tail = quicklist->tail;
if (_quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz)) {
quicklist->tail->zl = quicklist->tail->zl =
ziplistPush(quicklist->tail->zl, value, sz, ZIPLIST_TAIL); ziplistPush(quicklist->tail->zl, value, sz, ZIPLIST_TAIL);
quicklistNodeUpdateSz(quicklist->tail);
} else { } else {
quicklistNode *node = quicklistCreateNode(); quicklistNode *node = quicklistCreateNode();
node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_TAIL); node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_TAIL);
quicklistNodeUpdateSz(node);
_quicklistInsertNodeAfter(quicklist, quicklist->tail, node); _quicklistInsertNodeAfter(quicklist, quicklist->tail, node);
} }
quicklist->count++; quicklist->count++;
quicklist->tail->count++; quicklist->tail->count++;
quicklistNodeUpdateSz(quicklist->tail); return (orig_tail != quicklist->tail);
return quicklist;
} }
/* Create new node consisting of a pre-formed ziplist. /* Create new node consisting of a pre-formed ziplist.
...@@ -293,7 +523,7 @@ void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl) { ...@@ -293,7 +523,7 @@ void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl) {
* *
* Returns 'quicklist' argument. Frees passed-in ziplist 'zl' */ * Returns 'quicklist' argument. Frees passed-in ziplist 'zl' */
quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist, quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist,
const int fill, unsigned char *zl) { unsigned char *zl) {
unsigned char *value; unsigned char *value;
unsigned int sz; unsigned int sz;
long long longval; long long longval;
...@@ -306,7 +536,7 @@ quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist, ...@@ -306,7 +536,7 @@ quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist,
sz = ll2string(longstr, sizeof(longstr), longval); sz = ll2string(longstr, sizeof(longstr), longval);
value = (unsigned char *)longstr; value = (unsigned char *)longstr;
} }
quicklistPushTail(quicklist, fill, value, sz); quicklistPushTail(quicklist, value, sz);
p = ziplistNext(zl, p); p = ziplistNext(zl, p);
} }
zfree(zl); zfree(zl);
...@@ -316,14 +546,16 @@ quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist, ...@@ -316,14 +546,16 @@ quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist,
/* Create new (potentially multi-node) quicklist from a single existing ziplist. /* Create new (potentially multi-node) quicklist from a single existing ziplist.
* *
* Returns new quicklist. Frees passed-in ziplist 'zl'. */ * Returns new quicklist. Frees passed-in ziplist 'zl'. */
quicklist *quicklistCreateFromZiplist(int fill, unsigned char *zl) { quicklist *quicklistCreateFromZiplist(int fill, int compress,
return quicklistAppendValuesFromZiplist(quicklistCreate(), fill, zl); unsigned char *zl) {
return quicklistAppendValuesFromZiplist(quicklistNew(fill, compress), zl);
} }
#define quicklistDeleteIfEmpty(ql, n) \ #define quicklistDeleteIfEmpty(ql, n) \
do { \ do { \
if ((n)->count == 0) { \ if ((n)->count == 0) { \
__quicklistDelNode((ql), (n)); \ __quicklistDelNode((ql), (n)); \
(n) = NULL; \
} \ } \
} while (0) } while (0)
...@@ -333,11 +565,17 @@ static void __quicklistDelNode(quicklist *quicklist, quicklistNode *node) { ...@@ -333,11 +565,17 @@ static void __quicklistDelNode(quicklist *quicklist, quicklistNode *node) {
if (node->prev) if (node->prev)
node->prev->next = node->next; node->prev->next = node->next;
if (node == quicklist->tail) if (node == quicklist->tail) {
quicklist->tail = node->prev; quicklist->tail = node->prev;
}
if (node == quicklist->head) if (node == quicklist->head) {
quicklist->head = node->next; quicklist->head = node->next;
}
/* If we deleted a node within our compress depth, we
* now have compressed nodes needing to be decompressed. */
__quicklistCompress(quicklist, NULL);
quicklist->count -= node->count; quicklist->count -= node->count;
...@@ -349,21 +587,25 @@ static void __quicklistDelNode(quicklist *quicklist, quicklistNode *node) { ...@@ -349,21 +587,25 @@ static void __quicklistDelNode(quicklist *quicklist, quicklistNode *node) {
/* Delete one entry from list given the node for the entry and a pointer /* Delete one entry from list given the node for the entry and a pointer
* to the entry in the node. * to the entry in the node.
* *
* Note: quicklistDelIndex() *requires* uncompressed nodes because you
* already had to get *p from an uncompressed node somewhere.
*
* Returns 1 if the entire node was deleted, 0 if node still exists. * Returns 1 if the entire node was deleted, 0 if node still exists.
* Also updates in/out param 'p' with the next offset in the ziplist. */ * Also updates in/out param 'p' with the next offset in the ziplist. */
static int quicklistDelIndex(quicklist *quicklist, quicklistNode *node, static int quicklistDelIndex(quicklist *quicklist, quicklistNode *node,
unsigned char **p) { unsigned char **p) {
int gone = 0; int gone = 0;
node->zl = ziplistDelete(node->zl, p); node->zl = ziplistDelete(node->zl, p);
node->count--; node->count--;
if (node->count == 0) { if (node->count == 0) {
gone = 1; gone = 1;
__quicklistDelNode(quicklist, node);
} else { } else {
quicklistNodeUpdateSz(node); quicklistNodeUpdateSz(node);
} }
quicklist->count--; quicklist->count--;
quicklistDeleteIfEmpty(quicklist, node); /* If we deleted the node, the original node is no longer valid */
/* If we deleted all the nodes, our returned pointer is no longer valid */
return gone ? 1 : 0; return gone ? 1 : 0;
} }
...@@ -408,8 +650,10 @@ int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data, ...@@ -408,8 +650,10 @@ int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data,
int sz) { int sz) {
quicklistEntry entry; quicklistEntry entry;
if (quicklistIndex(quicklist, index, &entry)) { if (quicklistIndex(quicklist, index, &entry)) {
// quicklistDecompressNode(entry.node);
entry.node->zl = ziplistDelete(entry.node->zl, &entry.zi); entry.node->zl = ziplistDelete(entry.node->zl, &entry.zi);
entry.node->zl = ziplistInsert(entry.node->zl, entry.zi, data, sz); entry.node->zl = ziplistInsert(entry.node->zl, entry.zi, data, sz);
quicklistCompress(quicklist, entry.node);
return 1; return 1;
} else { } else {
return 0; return 0;
...@@ -434,6 +678,8 @@ static quicklistNode *_quicklistZiplistMerge(quicklist *quicklist, ...@@ -434,6 +678,8 @@ static quicklistNode *_quicklistZiplistMerge(quicklist *quicklist,
quicklistNode *b) { quicklistNode *b) {
D("Requested merge (a,b) (%u, %u)", a->count, b->count); D("Requested merge (a,b) (%u, %u)", a->count, b->count);
quicklistDecompressNode(a);
quicklistDecompressNode(b);
if ((ziplistMerge(&a->zl, &b->zl))) { if ((ziplistMerge(&a->zl, &b->zl))) {
/* We merged ziplists! Now remove the unused quicklistNode. */ /* We merged ziplists! Now remove the unused quicklistNode. */
quicklistNode *keep = NULL, *nokeep = NULL; quicklistNode *keep = NULL, *nokeep = NULL;
...@@ -449,7 +695,7 @@ static quicklistNode *_quicklistZiplistMerge(quicklist *quicklist, ...@@ -449,7 +695,7 @@ static quicklistNode *_quicklistZiplistMerge(quicklist *quicklist,
nokeep->count = 0; nokeep->count = 0;
__quicklistDelNode(quicklist, nokeep); __quicklistDelNode(quicklist, nokeep);
quicklistCompress(quicklist, keep);
return keep; return keep;
} else { } else {
/* else, the merge returned NULL and nothing changed. */ /* else, the merge returned NULL and nothing changed. */
...@@ -465,8 +711,8 @@ static quicklistNode *_quicklistZiplistMerge(quicklist *quicklist, ...@@ -465,8 +711,8 @@ static quicklistNode *_quicklistZiplistMerge(quicklist *quicklist,
* - (center->prev, center) * - (center->prev, center)
* - (center, center->next) * - (center, center->next)
*/ */
static void _quicklistMergeNodes(quicklist *quicklist, const int fill, static void _quicklistMergeNodes(quicklist *quicklist, quicklistNode *center) {
quicklistNode *center) { int fill = quicklist->fill;
quicklistNode *prev, *prev_prev, *next, *next_next, *target; quicklistNode *prev, *prev_prev, *next, *next_next, *target;
prev = prev_prev = next = next_next = target = NULL; prev = prev_prev = next = next_next = target = NULL;
...@@ -530,7 +776,7 @@ static void _quicklistMergeNodes(quicklist *quicklist, const int fill, ...@@ -530,7 +776,7 @@ static void _quicklistMergeNodes(quicklist *quicklist, const int fill,
* Returns newly created node or NULL if split not possible. */ * Returns newly created node or NULL if split not possible. */
static quicklistNode *_quicklistSplitNode(quicklistNode *node, int offset, static quicklistNode *_quicklistSplitNode(quicklistNode *node, int offset,
int after) { int after) {
size_t zl_sz = ziplistBlobLen(node->zl); size_t zl_sz = node->sz;
quicklistNode *new_node = quicklistCreateNode(); quicklistNode *new_node = quicklistCreateNode();
new_node->zl = zmalloc(zl_sz); new_node->zl = zmalloc(zl_sz);
...@@ -563,10 +809,10 @@ static quicklistNode *_quicklistSplitNode(quicklistNode *node, int offset, ...@@ -563,10 +809,10 @@ static quicklistNode *_quicklistSplitNode(quicklistNode *node, int offset,
* *
* If after==1, the new value is inserted after 'entry', otherwise * If after==1, the new value is inserted after 'entry', otherwise
* the new value is inserted before 'entry'. */ * the new value is inserted before 'entry'. */
static void _quicklistInsert(quicklist *quicklist, const int fill, static void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry,
quicklistEntry *entry, void *value, void *value, const size_t sz, int after) {
const size_t sz, int after) {
int full = 0, at_tail = 0, at_head = 0, full_next = 0, full_prev = 0; int full = 0, at_tail = 0, at_head = 0, full_next = 0, full_prev = 0;
int fill = quicklist->fill;
quicklistNode *node = entry->node; quicklistNode *node = entry->node;
quicklistNode *new_node = NULL; quicklistNode *new_node = NULL;
...@@ -588,7 +834,7 @@ static void _quicklistInsert(quicklist *quicklist, const int fill, ...@@ -588,7 +834,7 @@ static void _quicklistInsert(quicklist *quicklist, const int fill,
full = 1; full = 1;
} }
if (after && (ziplistNext(node->zl, entry->zi) == NULL)) { if (after && (entry->offset == node->count)) {
D("At Tail of current ziplist"); D("At Tail of current ziplist");
at_tail = 1; at_tail = 1;
if (!_quicklistNodeAllowInsert(node->next, fill, sz)) { if (!_quicklistNodeAllowInsert(node->next, fill, sz)) {
...@@ -597,7 +843,7 @@ static void _quicklistInsert(quicklist *quicklist, const int fill, ...@@ -597,7 +843,7 @@ static void _quicklistInsert(quicklist *quicklist, const int fill,
} }
} }
if (!after && (ziplistPrev(node->zl, entry->zi) == NULL)) { if (!after && (entry->offset == 0)) {
D("At Head"); D("At Head");
at_head = 1; at_head = 1;
if (!_quicklistNodeAllowInsert(node->prev, fill, sz)) { if (!_quicklistNodeAllowInsert(node->prev, fill, sz)) {
...@@ -609,6 +855,7 @@ static void _quicklistInsert(quicklist *quicklist, const int fill, ...@@ -609,6 +855,7 @@ static void _quicklistInsert(quicklist *quicklist, const int fill,
/* Now determine where and how to insert the new element */ /* Now determine where and how to insert the new element */
if (!full && after) { if (!full && after) {
D("Not full, inserting after current position."); D("Not full, inserting after current position.");
quicklistDecompressNodeForUse(node);
unsigned char *next = ziplistNext(node->zl, entry->zi); unsigned char *next = ziplistNext(node->zl, entry->zi);
if (next == NULL) { if (next == NULL) {
node->zl = ziplistPush(node->zl, value, sz, ZIPLIST_TAIL); node->zl = ziplistPush(node->zl, value, sz, ZIPLIST_TAIL);
...@@ -617,27 +864,34 @@ static void _quicklistInsert(quicklist *quicklist, const int fill, ...@@ -617,27 +864,34 @@ static void _quicklistInsert(quicklist *quicklist, const int fill,
} }
node->count++; node->count++;
quicklistNodeUpdateSz(node); quicklistNodeUpdateSz(node);
quicklistRecompressOnly(quicklist, node);
} else if (!full && !after) { } else if (!full && !after) {
D("Not full, inserting before current position."); D("Not full, inserting before current position.");
quicklistDecompressNodeForUse(node);
node->zl = ziplistInsert(node->zl, entry->zi, value, sz); node->zl = ziplistInsert(node->zl, entry->zi, value, sz);
node->count++; node->count++;
quicklistNodeUpdateSz(node); quicklistNodeUpdateSz(node);
quicklistRecompressOnly(quicklist, node);
} else if (full && at_tail && node->next && !full_next && after) { } else if (full && at_tail && node->next && !full_next && after) {
/* If we are: at tail, next has free space, and inserting after: /* If we are: at tail, next has free space, and inserting after:
* - insert entry at head of next node. */ * - insert entry at head of next node. */
D("Full and tail, but next isn't full; inserting next node head"); D("Full and tail, but next isn't full; inserting next node head");
new_node = node->next; new_node = node->next;
quicklistDecompressNodeForUse(new_node);
new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_HEAD); new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_HEAD);
new_node->count++; new_node->count++;
quicklistNodeUpdateSz(new_node); quicklistNodeUpdateSz(new_node);
quicklistRecompressOnly(quicklist, new_node);
} else if (full && at_head && node->prev && !full_prev && !after) { } else if (full && at_head && node->prev && !full_prev && !after) {
/* If we are: at head, previous has free space, and inserting before: /* If we are: at head, previous has free space, and inserting before:
* - insert entry at tail of previous node. */ * - insert entry at tail of previous node. */
D("Full and head, but prev isn't full, inserting prev node tail"); D("Full and head, but prev isn't full, inserting prev node tail");
new_node = node->prev; new_node = node->prev;
quicklistDecompressNodeForUse(new_node);
new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_TAIL); new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_TAIL);
new_node->count++; new_node->count++;
quicklistNodeUpdateSz(new_node); quicklistNodeUpdateSz(new_node);
quicklistRecompressOnly(quicklist, new_node);
} else if (full && ((at_tail && node->next && full_next && after) || } else if (full && ((at_tail && node->next && full_next && after) ||
(at_head && node->prev && full_prev && !after))) { (at_head && node->prev && full_prev && !after))) {
/* If we are: full, and our prev/next is full, then: /* If we are: full, and our prev/next is full, then:
...@@ -652,27 +906,27 @@ static void _quicklistInsert(quicklist *quicklist, const int fill, ...@@ -652,27 +906,27 @@ static void _quicklistInsert(quicklist *quicklist, const int fill,
/* else, node is full we need to split it. */ /* else, node is full we need to split it. */
/* covers both after and !after cases */ /* covers both after and !after cases */
D("\tsplitting node..."); D("\tsplitting node...");
quicklistDecompressNodeForUse(node);
new_node = _quicklistSplitNode(node, entry->offset, after); new_node = _quicklistSplitNode(node, entry->offset, after);
new_node->zl = ziplistPush(new_node->zl, value, sz, new_node->zl = ziplistPush(new_node->zl, value, sz,
after ? ZIPLIST_HEAD : ZIPLIST_TAIL); after ? ZIPLIST_HEAD : ZIPLIST_TAIL);
new_node->count++; new_node->count++;
quicklistNodeUpdateSz(new_node); quicklistNodeUpdateSz(new_node);
__quicklistInsertNode(quicklist, node, new_node, after); __quicklistInsertNode(quicklist, node, new_node, after);
_quicklistMergeNodes(quicklist, fill, node); _quicklistMergeNodes(quicklist, node);
} }
quicklist->count++; quicklist->count++;
} }
void quicklistInsertBefore(quicklist *quicklist, const int fill, void quicklistInsertBefore(quicklist *quicklist, quicklistEntry *entry,
quicklistEntry *entry, void *value, void *value, const size_t sz) {
const size_t sz) { _quicklistInsert(quicklist, entry, value, sz, 0);
_quicklistInsert(quicklist, fill, entry, value, sz, 0);
} }
void quicklistInsertAfter(quicklist *quicklist, const int fill, void quicklistInsertAfter(quicklist *quicklist, quicklistEntry *entry,
quicklistEntry *entry, void *value, const size_t sz) { void *value, const size_t sz) {
_quicklistInsert(quicklist, fill, entry, value, sz, 1); _quicklistInsert(quicklist, entry, value, sz, 1);
} }
/* Delete a range of elements from the quicklist. /* Delete a range of elements from the quicklist.
...@@ -745,11 +999,14 @@ int quicklistDelRange(quicklist *quicklist, const long start, ...@@ -745,11 +999,14 @@ int quicklistDelRange(quicklist *quicklist, const long start,
if (delete_entire_node) { if (delete_entire_node) {
__quicklistDelNode(quicklist, node); __quicklistDelNode(quicklist, node);
} else { } else {
quicklistDecompressNodeForUse(node);
node->zl = ziplistDeleteRange(node->zl, entry.offset, del); node->zl = ziplistDeleteRange(node->zl, entry.offset, del);
node->count -= del;
quicklistNodeUpdateSz(node); quicklistNodeUpdateSz(node);
node->count -= del;
quicklist->count -= del; quicklist->count -= del;
quicklistDeleteIfEmpty(quicklist, node); quicklistDeleteIfEmpty(quicklist, node);
if (node)
quicklistRecompressOnly(quicklist, node);
} }
extent -= del; extent -= del;
...@@ -807,8 +1064,14 @@ quicklistIter *quicklistGetIteratorAtIdx(const quicklist *quicklist, ...@@ -807,8 +1064,14 @@ quicklistIter *quicklistGetIteratorAtIdx(const quicklist *quicklist,
} }
} }
/* Release iterator */ /* Release iterator.
void quicklistReleaseIterator(quicklistIter *iter) { zfree(iter); } * If we still have a valid current node, then re-encode current node. */
void quicklistReleaseIterator(quicklistIter *iter) {
if (iter->current)
quicklistCompress(iter->quicklist, iter->current);
zfree(iter);
}
/* Get next element in iterator. /* Get next element in iterator.
* *
...@@ -852,6 +1115,7 @@ int quicklistNext(quicklistIter *iter, quicklistEntry *entry) { ...@@ -852,6 +1115,7 @@ int quicklistNext(quicklistIter *iter, quicklistEntry *entry) {
if (!iter->zi) { if (!iter->zi) {
/* If !zi, use current index. */ /* If !zi, use current index. */
quicklistDecompressNodeForUse(iter->current);
iter->zi = ziplistIndex(iter->current->zl, iter->offset); iter->zi = ziplistIndex(iter->current->zl, iter->offset);
} else { } else {
/* else, use existing iterator offset and get prev/next as necessary. */ /* else, use existing iterator offset and get prev/next as necessary. */
...@@ -876,6 +1140,7 @@ int quicklistNext(quicklistIter *iter, quicklistEntry *entry) { ...@@ -876,6 +1140,7 @@ int quicklistNext(quicklistIter *iter, quicklistEntry *entry) {
} else { } else {
/* We ran out of ziplist entries. /* We ran out of ziplist entries.
* Pick next node, update offset, then re-run retrieval. */ * Pick next node, update offset, then re-run retrieval. */
quicklistCompress(iter->quicklist, iter->current);
if (iter->direction == AL_START_HEAD) { if (iter->direction == AL_START_HEAD) {
/* Forward traversal */ /* Forward traversal */
D("Jumping to start of next node"); D("Jumping to start of next node");
...@@ -901,19 +1166,26 @@ int quicklistNext(quicklistIter *iter, quicklistEntry *entry) { ...@@ -901,19 +1166,26 @@ int quicklistNext(quicklistIter *iter, quicklistEntry *entry) {
quicklist *quicklistDup(quicklist *orig) { quicklist *quicklistDup(quicklist *orig) {
quicklist *copy; quicklist *copy;
copy = quicklistCreate(); copy = quicklistNew(orig->fill, orig->compress);
for (quicklistNode *current = orig->head; current; for (quicklistNode *current = orig->head; current;
current = current->next) { current = current->next) {
quicklistNode *node = quicklistCreateNode(); quicklistNode *node = quicklistCreateNode();
size_t ziplen = ziplistBlobLen(current->zl); if (node->encoding == QUICKLIST_NODE_ENCODING_LZF) {
node->zl = zmalloc(ziplen); quicklistLZF *lzf = (quicklistLZF *)node->zl;
memcpy(node->zl, current->zl, ziplen); size_t lzf_sz = sizeof(*lzf) + lzf->sz;
node->zl = zmalloc(lzf_sz);
memcpy(node->zl, current->zl, lzf_sz);
} else if (node->encoding == QUICKLIST_NODE_ENCODING_RAW) {
node->zl = zmalloc(current->sz);
memcpy(node->zl, current->zl, current->sz);
}
node->count = current->count; node->count = current->count;
copy->count += node->count; copy->count += node->count;
node->sz = current->sz; node->sz = current->sz;
node->encoding = current->encoding;
_quicklistInsertNodeAfter(copy, copy->tail, node); _quicklistInsertNodeAfter(copy, copy->tail, node);
} }
...@@ -978,19 +1250,20 @@ int quicklistIndex(const quicklist *quicklist, const long long idx, ...@@ -978,19 +1250,20 @@ int quicklistIndex(const quicklist *quicklist, const long long idx,
entry->offset = (-index) - 1 + accum; entry->offset = (-index) - 1 + accum;
} }
quicklistDecompressNodeForUse(entry->node);
entry->zi = ziplistIndex(entry->node->zl, entry->offset); entry->zi = ziplistIndex(entry->node->zl, entry->offset);
ziplistGet(entry->zi, &entry->value, &entry->sz, &entry->longval); ziplistGet(entry->zi, &entry->value, &entry->sz, &entry->longval);
// quicklistCompress(quicklist, entry->node);
return 1; return 1;
} }
/* Rotate quicklist by moving the tail element to the head. */ /* Rotate quicklist by moving the tail element to the head. */
void quicklistRotate(quicklist *quicklist, const int fill) { void quicklistRotate(quicklist *quicklist) {
if (quicklist->count <= 1) if (quicklist->count <= 1)
return; return;
/* First, get the tail entry */ /* First, get the tail entry */
quicklistNode *tail = quicklist->tail; unsigned char *p = ziplistIndex(quicklist->tail->zl, -1);
unsigned char *p = ziplistIndex(tail->zl, -1);
unsigned char *value; unsigned char *value;
long long longval; long long longval;
unsigned int sz; unsigned int sz;
...@@ -1005,16 +1278,17 @@ void quicklistRotate(quicklist *quicklist, const int fill) { ...@@ -1005,16 +1278,17 @@ void quicklistRotate(quicklist *quicklist, const int fill) {
} }
/* Add tail entry to head (must happen before tail is deleted). */ /* Add tail entry to head (must happen before tail is deleted). */
quicklistPushHead(quicklist, fill, value, sz); quicklistPushHead(quicklist, value, sz);
/* If quicklist has only one node, the head ziplist is also the /* If quicklist has only one node, the head ziplist is also the
* tail ziplist and PushHead() could have reallocated our single ziplist, * tail ziplist and PushHead() could have reallocated our single ziplist,
* which would make our pre-existing 'p' unusable. */ * which would make our pre-existing 'p' unusable. */
if (quicklist->len == 1) if (quicklist->len == 1) {
p = ziplistIndex(tail->zl, -1); p = ziplistIndex(quicklist->tail->zl, -1);
}
/* Remove tail entry. */ /* Remove tail entry. */
quicklistDelIndex(quicklist, tail, &p); quicklistDelIndex(quicklist, quicklist->tail, &p);
} }
/* pop from quicklist and return result in 'data' ptr. Value of 'data' /* pop from quicklist and return result in 'data' ptr. Value of 'data'
...@@ -1106,18 +1380,19 @@ int quicklistPop(quicklist *quicklist, int where, unsigned char **data, ...@@ -1106,18 +1380,19 @@ int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
} }
/* Wrapper to allow argument-based switching between HEAD/TAIL pop */ /* Wrapper to allow argument-based switching between HEAD/TAIL pop */
void quicklistPush(quicklist *quicklist, const int fill, void *value, void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
const size_t sz, int where) { int where) {
if (where == QUICKLIST_HEAD) { if (where == QUICKLIST_HEAD) {
quicklistPushHead(quicklist, fill, value, sz); quicklistPushHead(quicklist, value, sz);
} else if (where == QUICKLIST_TAIL) { } else if (where == QUICKLIST_TAIL) {
quicklistPushTail(quicklist, fill, value, sz); quicklistPushTail(quicklist, value, sz);
} }
} }
/* The rest of this file is test cases and test helpers. */ /* The rest of this file is test cases and test helpers. */
#ifdef REDIS_TEST #ifdef REDIS_TEST
#include <stdint.h> #include <stdint.h>
#include <sys/time.h>
#define assert(_e) \ #define assert(_e) \
do { \ do { \
...@@ -1165,6 +1440,20 @@ static void ql_info(quicklist *ql) { ...@@ -1165,6 +1440,20 @@ static void ql_info(quicklist *ql) {
#endif #endif
} }
/* Return the UNIX time in microseconds */
static long long ustime(void) {
struct timeval tv;
long long ust;
gettimeofday(&tv, NULL);
ust = ((long long)tv.tv_sec) * 1000000;
ust += tv.tv_usec;
return ust;
}
/* Return the UNIX time in milliseconds */
static long long mstime(void) { return ustime() / 1000; }
/* Iterate over an entire quicklist. /* Iterate over an entire quicklist.
* Print the list if 'print' == 1. * Print the list if 'print' == 1.
* *
...@@ -1207,17 +1496,17 @@ static int itrprintr_rev(quicklist *ql, int print) { ...@@ -1207,17 +1496,17 @@ static int itrprintr_rev(quicklist *ql, int print) {
/* Verify list metadata matches physical list contents. */ /* Verify list metadata matches physical list contents. */
static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count, static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count,
uint32_t head_count, uint32_t tail_count) { uint32_t head_count, uint32_t tail_count) {
int ok = 1; int errors = 0;
ql_info(ql); ql_info(ql);
if (len != ql->len) { if (len != ql->len) {
yell("quicklist length wrong: expected %d, got %lu", len, ql->len); yell("quicklist length wrong: expected %d, got %u", len, ql->len);
ok = 0; errors++;
} }
if (count != ql->count) { if (count != ql->count) {
yell("quicklist count wrong: expected %d, got %lu", count, ql->count); yell("quicklist count wrong: expected %d, got %lu", count, ql->count);
ok = 0; errors++;
} }
int loopr = itrprintr(ql, 0); int loopr = itrprintr(ql, 0);
...@@ -1225,7 +1514,7 @@ static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count, ...@@ -1225,7 +1514,7 @@ static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count,
yell("quicklist cached count not match actual count: expected %lu, got " yell("quicklist cached count not match actual count: expected %lu, got "
"%d", "%d",
ql->count, loopr); ql->count, loopr);
ok = 0; errors++;
} }
int rloopr = itrprintr_rev(ql, 0); int rloopr = itrprintr_rev(ql, 0);
...@@ -1233,32 +1522,62 @@ static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count, ...@@ -1233,32 +1522,62 @@ static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count,
yell("quicklist has different forward count than reverse count! " yell("quicklist has different forward count than reverse count! "
"Forward count is %d, reverse count is %d.", "Forward count is %d, reverse count is %d.",
loopr, rloopr); loopr, rloopr);
ok = 0; errors++;
} }
if (ql->len == 0 && ok) { if (ql->len == 0 && !errors) {
OK; OK;
return !ok; return errors;
} }
if (head_count != ql->head->count && if (ql->head && head_count != ql->head->count &&
head_count != ziplistLen(ql->head->zl)) { head_count != ziplistLen(ql->head->zl)) {
yell("quicklist head count wrong: expected %d, " yell("quicklist head count wrong: expected %d, "
"got cached %d vs. actual %d", "got cached %d vs. actual %d",
head_count, ql->head->count, ziplistLen(ql->head->zl)); head_count, ql->head->count, ziplistLen(ql->head->zl));
ok = 0; errors++;
} }
if (tail_count != ql->tail->count && if (ql->tail && tail_count != ql->tail->count &&
tail_count != ziplistLen(ql->tail->zl)) { tail_count != ziplistLen(ql->tail->zl)) {
yell("quicklist tail count wrong: expected %d, " yell("quicklist tail count wrong: expected %d, "
"got cached %u vs. actual %d", "got cached %u vs. actual %d",
tail_count, ql->tail->count, ziplistLen(ql->tail->zl)); tail_count, ql->tail->count, ziplistLen(ql->tail->zl));
ok = 0; errors++;
}
if (quicklistAllowsCompression(ql)) {
quicklistNode *node = ql->head;
unsigned int low_raw = ql->compress;
unsigned int high_raw = ql->len - ql->compress;
for (unsigned int at = 0; at < ql->len; at++, node = node->next) {
if (node && (at < low_raw || at >= high_raw)) {
if (node->encoding != QUICKLIST_NODE_ENCODING_RAW) {
yell("Incorrect compression: node %d is "
"compressed at depth %d ((%u, %u); total "
"nodes: %u; size: %u; recompress: %d)",
at, ql->compress, low_raw, high_raw, ql->len, node->sz,
node->recompress);
errors++;
}
} else {
if (node->encoding != QUICKLIST_NODE_ENCODING_LZF &&
!node->attempted_compress) {
yell("Incorrect non-compression: node %d is NOT "
"compressed at depth %d ((%u, %u); total "
"nodes: %u; size: %u; recompress: %d; attempted: %d)",
at, ql->compress, low_raw, high_raw, ql->len, node->sz,
node->recompress, node->attempted_compress);
errors++;
}
}
}
} }
if (ok)
if (!errors)
OK; OK;
return !ok; return errors;
} }
/* Generate new string concatenating integer i against string 'prefix' */ /* Generate new string concatenating integer i against string 'prefix' */
...@@ -1268,920 +1587,1027 @@ static char *genstr(char *prefix, int i) { ...@@ -1268,920 +1587,1027 @@ static char *genstr(char *prefix, int i) {
return result; return result;
} }
/* Test fill cap */
#define F 32
/* main test, but callable from other files */ /* main test, but callable from other files */
int quicklistTest(int argc, char *argv[]) { int quicklistTest(int argc, char *argv[]) {
UNUSED(argc);
UNUSED(argv);
unsigned int err = 0; unsigned int err = 0;
int optimize_start = int optimize_start =
-(int)(sizeof(optimization_level) / sizeof(*optimization_level)); -(int)(sizeof(optimization_level) / sizeof(*optimization_level));
printf("Starting optimization offset at: %d\n", optimize_start); printf("Starting optimization offset at: %d\n", optimize_start);
UNUSED(argc); int options[] = { 0, 1, 2, 3, 4, 5, 6, 10 };
UNUSED(argv); size_t option_count = sizeof(options) / sizeof(*options);
long long runtime[option_count];
TEST("create list") { for (int _i = 0; _i < (int)option_count; _i++) {
quicklist *ql = quicklistCreate(); printf("Testing Option %d\n", options[_i]);
ql_verify(ql, 0, 0, 0, 0); long long start = mstime();
quicklistRelease(ql);
}
TEST("add to tail of empty list") { TEST("create list") {
quicklist *ql = quicklistCreate(); quicklist *ql = quicklistNew(-2, options[_i]);
quicklistPushTail(ql, F, "hello", 6); ql_verify(ql, 0, 0, 0, 0);
/* 1 for head and 1 for tail beacuse 1 node = head = tail */
ql_verify(ql, 1, 1, 1, 1);
quicklistRelease(ql);
}
TEST("add to head of empty list") {
quicklist *ql = quicklistCreate();
quicklistPushHead(ql, F, "hello", 6);
/* 1 for head and 1 for tail beacuse 1 node = head = tail */
ql_verify(ql, 1, 1, 1, 1);
quicklistRelease(ql);
}
for (int f = optimize_start; f < 32; f++) {
TEST_DESC("add to tail 5x at fill %d", f) {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 5; i++)
quicklistPushTail(ql, f, genstr("hello", i), 32);
if (ql->count != 5)
ERROR;
if (f == 32)
ql_verify(ql, 1, 5, 5, 5);
quicklistRelease(ql); quicklistRelease(ql);
} }
}
for (int f = optimize_start; f < 32; f++) { TEST("add to tail of empty list") {
TEST_DESC("add to head 5x at fill %d", f) { quicklist *ql = quicklistNew(-2, options[_i]);
quicklist *ql = quicklistCreate(); quicklistPushTail(ql, "hello", 6);
for (int i = 0; i < 5; i++) /* 1 for head and 1 for tail beacuse 1 node = head = tail */
quicklistPushHead(ql, f, genstr("hello", i), 32); ql_verify(ql, 1, 1, 1, 1);
if (ql->count != 5)
ERROR;
if (f == 32)
ql_verify(ql, 1, 5, 5, 5);
quicklistRelease(ql); quicklistRelease(ql);
} }
}
for (int f = optimize_start; f < 512; f++) { TEST("add to head of empty list") {
TEST_DESC("add to tail 500x at fill %d", f) { quicklist *ql = quicklistNew(-2, options[_i]);
quicklist *ql = quicklistCreate(); quicklistPushHead(ql, "hello", 6);
for (int i = 0; i < 500; i++) /* 1 for head and 1 for tail beacuse 1 node = head = tail */
quicklistPushTail(ql, f, genstr("hello", i), 32); ql_verify(ql, 1, 1, 1, 1);
if (ql->count != 500)
ERROR;
if (f == 32)
ql_verify(ql, 16, 500, 32, 20);
quicklistRelease(ql); quicklistRelease(ql);
} }
}
for (int f = optimize_start; f < 512; f++) { for (int f = optimize_start; f < 32; f++) {
TEST_DESC("add to head 500x at fill %d", f) { TEST_DESC("add to tail 5x at fill %d at compress %d", f,
quicklist *ql = quicklistCreate(); options[_i]) {
for (int i = 0; i < 500; i++) quicklist *ql = quicklistNew(f, options[_i]);
quicklistPushHead(ql, f, genstr("hello", i), 32); for (int i = 0; i < 5; i++)
if (ql->count != 500) quicklistPushTail(ql, genstr("hello", i), 32);
ERROR; if (ql->count != 5)
if (f == 32) ERROR;
ql_verify(ql, 16, 500, 20, 32); if (f == 32)
quicklistRelease(ql); ql_verify(ql, 1, 5, 5, 5);
quicklistRelease(ql);
}
} }
}
TEST("rotate empty") { for (int f = optimize_start; f < 32; f++) {
quicklist *ql = quicklistCreate(); TEST_DESC("add to head 5x at fill %d at compress %d", f,
quicklistRotate(ql, F); options[_i]) {
ql_verify(ql, 0, 0, 0, 0); quicklist *ql = quicklistNew(f, options[_i]);
quicklistRelease(ql); for (int i = 0; i < 5; i++)
} quicklistPushHead(ql, genstr("hello", i), 32);
if (ql->count != 5)
ERROR;
if (f == 32)
ql_verify(ql, 1, 5, 5, 5);
quicklistRelease(ql);
}
}
for (int f = optimize_start; f < 32; f++) { for (int f = optimize_start; f < 512; f++) {
TEST("rotate one val once") { TEST_DESC("add to tail 500x at fill %d at compress %d", f,
quicklist *ql = quicklistCreate(); options[_i]) {
quicklistPushHead(ql, F, "hello", 6); quicklist *ql = quicklistNew(f, options[_i]);
quicklistRotate(ql, F); for (int i = 0; i < 500; i++)
ql_verify(ql, 1, 1, 1, 1); quicklistPushTail(ql, genstr("hello", i), 64);
quicklistRelease(ql); if (ql->count != 500)
ERROR;
if (f == 32)
ql_verify(ql, 16, 500, 32, 20);
quicklistRelease(ql);
}
} }
}
for (int f = optimize_start; f < 1024; f++) { for (int f = optimize_start; f < 512; f++) {
TEST_DESC("rotate 500 val 5000 times at fill %d", f) { TEST_DESC("add to head 500x at fill %d at compress %d", f,
quicklist *ql = quicklistCreate(); options[_i]) {
quicklistPushHead(ql, f, "900", 3); quicklist *ql = quicklistNew(f, options[_i]);
quicklistPushHead(ql, f, "7000", 4); for (int i = 0; i < 500; i++)
quicklistPushHead(ql, f, "-1200", 5); quicklistPushHead(ql, genstr("hello", i), 32);
quicklistPushHead(ql, f, "42", 2); if (ql->count != 500)
for (int i = 0; i < 500; i++) ERROR;
quicklistPushHead(ql, f, genstr("hello", i), 32); if (f == 32)
ql_info(ql); ql_verify(ql, 16, 500, 20, 32);
for (int i = 0; i < 5000; i++) { quicklistRelease(ql);
ql_info(ql);
quicklistRotate(ql, f);
} }
if (f == 1) }
ql_verify(ql, 504, 504, 1, 1);
else if (f == 2) TEST("rotate empty") {
ql_verify(ql, 252, 504, 2, 2); quicklist *ql = quicklistNew(-2, options[_i]);
else if (f == 32) quicklistRotate(ql);
ql_verify(ql, 16, 504, 32, 24); ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql); quicklistRelease(ql);
} }
}
TEST("pop empty") { for (int f = optimize_start; f < 32; f++) {
quicklist *ql = quicklistCreate(); TEST("rotate one val once") {
quicklistPop(ql, QUICKLIST_HEAD, NULL, NULL, NULL); quicklist *ql = quicklistNew(f, options[_i]);
ql_verify(ql, 0, 0, 0, 0); quicklistPushHead(ql, "hello", 6);
quicklistRelease(ql); quicklistRotate(ql);
} /* Ignore compression verify because ziplist is
* too small to compress. */
ql_verify(ql, 1, 1, 1, 1);
quicklistRelease(ql);
}
}
TEST("pop 1 string from 1") { for (int f = optimize_start; f < 3; f++) {
quicklist *ql = quicklistCreate(); TEST_DESC("rotate 500 val 5000 times at fill %d at compress %d", f,
quicklistPushHead(ql, F, genstr("hello", 331), 32); options[_i]) {
unsigned char *data; quicklist *ql = quicklistNew(f, options[_i]);
unsigned int sz; quicklistPushHead(ql, "900", 3);
long long lv; quicklistPushHead(ql, "7000", 4);
ql_info(ql); quicklistPushHead(ql, "-1200", 5);
quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv); quicklistPushHead(ql, "42", 2);
assert(data != NULL); for (int i = 0; i < 500; i++)
assert(sz == 32); quicklistPushHead(ql, genstr("hello", i), 64);
zfree(data); ql_info(ql);
ql_verify(ql, 0, 0, 0, 0); for (int i = 0; i < 5000; i++) {
quicklistRelease(ql); ql_info(ql);
} quicklistRotate(ql);
}
if (f == 1)
ql_verify(ql, 504, 504, 1, 1);
else if (f == 2)
ql_verify(ql, 252, 504, 2, 2);
else if (f == 32)
ql_verify(ql, 16, 504, 32, 24);
quicklistRelease(ql);
}
}
TEST("pop head 1 number from 1") { TEST("pop empty") {
quicklist *ql = quicklistCreate(); quicklist *ql = quicklistNew(-2, options[_i]);
quicklistPushHead(ql, F, "55513", 5); quicklistPop(ql, QUICKLIST_HEAD, NULL, NULL, NULL);
unsigned char *data; ql_verify(ql, 0, 0, 0, 0);
unsigned int sz; quicklistRelease(ql);
long long lv; }
ql_info(ql);
quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
assert(data == NULL);
assert(lv == 55513);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("pop head 500 from 500") { TEST("pop 1 string from 1") {
quicklist *ql = quicklistCreate(); quicklist *ql = quicklistNew(-2, options[_i]);
for (int i = 0; i < 500; i++) quicklistPushHead(ql, genstr("hello", 331), 32);
quicklistPushHead(ql, F, genstr("hello", i), 32);
ql_info(ql);
for (int i = 0; i < 500; i++) {
unsigned char *data; unsigned char *data;
unsigned int sz; unsigned int sz;
long long lv; long long lv;
int ret = quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv); ql_info(ql);
assert(ret == 1); quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
assert(data != NULL); assert(data != NULL);
assert(sz == 32); assert(sz == 32);
zfree(data); zfree(data);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
} }
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("pop head 5000 from 500") { TEST("pop head 1 number from 1") {
quicklist *ql = quicklistCreate(); quicklist *ql = quicklistNew(-2, options[_i]);
for (int i = 0; i < 500; i++) quicklistPushHead(ql, "55513", 5);
quicklistPushHead(ql, F, genstr("hello", i), 32);
for (int i = 0; i < 5000; i++) {
unsigned char *data; unsigned char *data;
unsigned int sz; unsigned int sz;
long long lv; long long lv;
int ret = quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv); ql_info(ql);
if (i < 500) { quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
assert(data == NULL);
assert(lv == 55513);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("pop head 500 from 500") {
quicklist *ql = quicklistNew(-2, options[_i]);
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, genstr("hello", i), 32);
ql_info(ql);
for (int i = 0; i < 500; i++) {
unsigned char *data;
unsigned int sz;
long long lv;
int ret = quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
assert(ret == 1); assert(ret == 1);
assert(data != NULL); assert(data != NULL);
assert(sz == 32); assert(sz == 32);
zfree(data); zfree(data);
} else {
assert(ret == 0);
} }
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
} }
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("iterate forward over 500 list") {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, F, genstr("hello", i), 32);
quicklistIter *iter = quicklistGetIterator(ql, AL_START_HEAD);
quicklistEntry entry;
int i = 499, count = 0;
while (quicklistNext(iter, &entry)) {
char *h = genstr("hello", i);
if (strcmp((char *)entry.value, h))
ERR("value [%s] didn't match [%s] at position %d", entry.value,
h, i);
i--;
count++;
}
if (count != 500)
ERR("Didn't iterate over exactly 500 elements (%d)", i);
ql_verify(ql, 16, 500, 20, 32);
quicklistReleaseIterator(iter);
quicklistRelease(ql);
}
TEST("iterate reverse over 500 list") {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, F, genstr("hello", i), 32);
quicklistIter *iter = quicklistGetIterator(ql, AL_START_TAIL);
quicklistEntry entry;
int i = 0;
while (quicklistNext(iter, &entry)) {
char *h = genstr("hello", i);
if (strcmp((char *)entry.value, h))
ERR("value [%s] didn't match [%s] at position %d", entry.value,
h, i);
i++;
}
if (i != 500)
ERR("Didn't iterate over exactly 500 elements (%d)", i);
ql_verify(ql, 16, 500, 20, 32);
quicklistReleaseIterator(iter);
quicklistRelease(ql);
}
TEST("insert before with 0 elements") {
quicklist *ql = quicklistCreate();
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
quicklistInsertBefore(ql, F, &entry, "abc", 4);
ql_verify(ql, 1, 1, 1, 1);
quicklistRelease(ql);
}
TEST("insert after with 0 elements") {
quicklist *ql = quicklistCreate();
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
quicklistInsertAfter(ql, F, &entry, "abc", 4);
ql_verify(ql, 1, 1, 1, 1);
quicklistRelease(ql);
}
TEST("insert after 1 element") { TEST("pop head 5000 from 500") {
quicklist *ql = quicklistCreate(); quicklist *ql = quicklistNew(-2, options[_i]);
quicklistPushHead(ql, F, "hello", 6); for (int i = 0; i < 500; i++)
quicklistEntry entry; quicklistPushHead(ql, genstr("hello", i), 32);
quicklistIndex(ql, 0, &entry); for (int i = 0; i < 5000; i++) {
quicklistInsertAfter(ql, F, &entry, "abc", 4); unsigned char *data;
ql_verify(ql, 1, 2, 2, 2); unsigned int sz;
quicklistRelease(ql); long long lv;
} int ret = quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
if (i < 500) {
TEST("insert before 1 element") { assert(ret == 1);
quicklist *ql = quicklistCreate(); assert(data != NULL);
quicklistPushHead(ql, F, "hello", 6); assert(sz == 32);
quicklistEntry entry; zfree(data);
quicklistIndex(ql, 0, &entry); } else {
quicklistInsertAfter(ql, F, &entry, "abc", 4); assert(ret == 0);
ql_verify(ql, 1, 2, 2, 2);
quicklistRelease(ql);
}
for (int f = optimize_start; f < 12; f++) {
TEST_DESC("insert once in elements while iterating at fill %d\n", f) {
quicklist *ql = quicklistCreate();
quicklistPushTail(ql, f, "abc", 3);
quicklistPushTail(ql, 1, "def", 3); /* force to unique node */
quicklistPushTail(ql, f, "bob", 3); /* force to reset for +3 */
quicklistPushTail(ql, f, "foo", 3);
quicklistPushTail(ql, f, "zoo", 3);
itrprintr(ql, 1);
/* insert "bar" before "bob" while iterating over list. */
quicklistIter *iter = quicklistGetIterator(ql, AL_START_HEAD);
quicklistEntry entry;
while (quicklistNext(iter, &entry)) {
if (!strncmp((char *)entry.value, "bob", 3)) {
/* Insert as fill = 1 so it spills into new node. */
quicklistInsertBefore(ql, f, &entry, "bar", 3);
/* NOTE! You can't continue iterating after an insert into
* the list. You *must* re-create your iterator again if
* you want to traverse all entires. */
break;
} }
} }
itrprintr(ql, 1); ql_verify(ql, 0, 0, 0, 0);
/* verify results */
quicklistIndex(ql, 0, &entry);
if (strncmp((char *)entry.value, "abc", 3))
ERR("Value 0 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistIndex(ql, 1, &entry);
if (strncmp((char *)entry.value, "def", 3))
ERR("Value 1 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistIndex(ql, 2, &entry);
if (strncmp((char *)entry.value, "bar", 3))
ERR("Value 2 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistIndex(ql, 3, &entry);
if (strncmp((char *)entry.value, "bob", 3))
ERR("Value 3 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistIndex(ql, 4, &entry);
if (strncmp((char *)entry.value, "foo", 3))
ERR("Value 4 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistIndex(ql, 5, &entry);
if (strncmp((char *)entry.value, "zoo", 3))
ERR("Value 5 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistReleaseIterator(iter);
quicklistRelease(ql); quicklistRelease(ql);
} }
}
for (int f = optimize_start; f < 1024; f++) { TEST("iterate forward over 500 list") {
TEST_DESC("insert [before] 250 new in middle of 500 elements at fill" quicklist *ql = quicklistNew(-2, options[_i]);
" %d", quicklistSetFill(ql, 32);
f) {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++) for (int i = 0; i < 500; i++)
quicklistPushTail(ql, f, genstr("hello", i), 32); quicklistPushHead(ql, genstr("hello", i), 32);
for (int i = 0; i < 250; i++) { quicklistIter *iter = quicklistGetIterator(ql, AL_START_HEAD);
quicklistEntry entry; quicklistEntry entry;
quicklistIndex(ql, 250, &entry); int i = 499, count = 0;
quicklistInsertBefore(ql, f, &entry, genstr("abc", i), 32); while (quicklistNext(iter, &entry)) {
char *h = genstr("hello", i);
if (strcmp((char *)entry.value, h))
ERR("value [%s] didn't match [%s] at position %d",
entry.value, h, i);
i--;
count++;
} }
if (f == 32) if (count != 500)
ql_verify(ql, 25, 750, 32, 20); ERR("Didn't iterate over exactly 500 elements (%d)", i);
ql_verify(ql, 16, 500, 20, 32);
quicklistReleaseIterator(iter);
quicklistRelease(ql); quicklistRelease(ql);
} }
}
for (int f = optimize_start; f < 1024; f++) { TEST("iterate reverse over 500 list") {
TEST_DESC("insert [after] 250 new in middle of 500 elements at fill %d", quicklist *ql = quicklistNew(-2, options[_i]);
f) { quicklistSetFill(ql, 32);
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++) for (int i = 0; i < 500; i++)
quicklistPushHead(ql, f, genstr("hello", i), 32); quicklistPushHead(ql, genstr("hello", i), 32);
for (int i = 0; i < 250; i++) { quicklistIter *iter = quicklistGetIterator(ql, AL_START_TAIL);
quicklistEntry entry; quicklistEntry entry;
quicklistIndex(ql, 250, &entry); int i = 0;
quicklistInsertAfter(ql, f, &entry, genstr("abc", i), 32); while (quicklistNext(iter, &entry)) {
char *h = genstr("hello", i);
if (strcmp((char *)entry.value, h))
ERR("value [%s] didn't match [%s] at position %d",
entry.value, h, i);
i++;
} }
if (i != 500)
if (ql->count != 750) ERR("Didn't iterate over exactly 500 elements (%d)", i);
ERR("List size not 750, but rather %ld", ql->count); ql_verify(ql, 16, 500, 20, 32);
quicklistReleaseIterator(iter);
if (f == 32)
ql_verify(ql, 26, 750, 20, 32);
quicklistRelease(ql); quicklistRelease(ql);
} }
}
TEST("duplicate empty list") {
quicklist *ql = quicklistCreate();
ql_verify(ql, 0, 0, 0, 0);
quicklist *copy = quicklistDup(ql);
ql_verify(copy, 0, 0, 0, 0);
quicklistRelease(ql);
quicklistRelease(copy);
}
TEST("duplicate list of 1 element") {
quicklist *ql = quicklistCreate();
quicklistPushHead(ql, F, genstr("hello", 3), 32);
ql_verify(ql, 1, 1, 1, 1);
quicklist *copy = quicklistDup(ql);
ql_verify(copy, 1, 1, 1, 1);
quicklistRelease(ql);
quicklistRelease(copy);
}
TEST("duplicate list of 500") {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, F, genstr("hello", i), 32);
ql_verify(ql, 16, 500, 20, 32);
quicklist *copy = quicklistDup(ql); TEST("insert before with 0 elements") {
ql_verify(copy, 16, 500, 20, 32); quicklist *ql = quicklistNew(-2, options[_i]);
quicklistRelease(ql);
quicklistRelease(copy);
}
for (int f = optimize_start; f < 512; f++) {
TEST_DESC("index 1,200 from 500 list at fill %d", f) {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, f, genstr("hello", i + 1), 32);
quicklistEntry entry; quicklistEntry entry;
quicklistIndex(ql, 1, &entry); quicklistIndex(ql, 0, &entry);
if (!strcmp((char *)entry.value, "hello2")) quicklistInsertBefore(ql, &entry, "abc", 4);
OK; ql_verify(ql, 1, 1, 1, 1);
else
ERR("Value: %s", entry.value);
quicklistIndex(ql, 200, &entry);
if (!strcmp((char *)entry.value, "hello201"))
OK;
else
ERR("Value: %s", entry.value);
quicklistRelease(ql); quicklistRelease(ql);
} }
TEST_DESC("index -1,-2 from 500 list at fill %d", f) { TEST("insert after with 0 elements") {
quicklist *ql = quicklistCreate(); quicklist *ql = quicklistNew(-2, options[_i]);
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, f, genstr("hello", i + 1), 32);
quicklistEntry entry; quicklistEntry entry;
quicklistIndex(ql, -1, &entry); quicklistIndex(ql, 0, &entry);
if (!strcmp((char *)entry.value, "hello500")) quicklistInsertAfter(ql, &entry, "abc", 4);
OK; ql_verify(ql, 1, 1, 1, 1);
else
ERR("Value: %s", entry.value);
quicklistIndex(ql, -2, &entry);
if (!strcmp((char *)entry.value, "hello499"))
OK;
else
ERR("Value: %s", entry.value);
quicklistRelease(ql); quicklistRelease(ql);
} }
TEST_DESC("index -100 from 500 list at fill %d", f) { TEST("insert after 1 element") {
quicklist *ql = quicklistCreate(); quicklist *ql = quicklistNew(-2, options[_i]);
for (int i = 0; i < 500; i++) quicklistPushHead(ql, "hello", 6);
quicklistPushTail(ql, f, genstr("hello", i + 1), 32);
quicklistEntry entry; quicklistEntry entry;
quicklistIndex(ql, -100, &entry); quicklistIndex(ql, 0, &entry);
if (!strcmp((char *)entry.value, "hello401")) quicklistInsertAfter(ql, &entry, "abc", 4);
OK; ql_verify(ql, 1, 2, 2, 2);
else
ERR("Value: %s", entry.value);
quicklistRelease(ql); quicklistRelease(ql);
} }
TEST_DESC("index too big +1 from 50 list at fill %d", f) { TEST("insert before 1 element") {
quicklist *ql = quicklistCreate(); quicklist *ql = quicklistNew(-2, options[_i]);
for (int i = 0; i < 50; i++) quicklistPushHead(ql, "hello", 6);
quicklistPushTail(ql, f, genstr("hello", i + 1), 32);
quicklistEntry entry; quicklistEntry entry;
if (quicklistIndex(ql, 50, &entry)) quicklistIndex(ql, 0, &entry);
ERR("Index found at 50 with 50 list: %.*s", entry.sz, quicklistInsertAfter(ql, &entry, "abc", 4);
entry.value); ql_verify(ql, 1, 2, 2, 2);
else
OK;
quicklistRelease(ql); quicklistRelease(ql);
} }
}
TEST("delete range empty list") {
quicklist *ql = quicklistCreate();
quicklistDelRange(ql, 5, 20);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("delete range of entire node in list of one node") {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 32; i++)
quicklistPushHead(ql, F, genstr("hello", i), 32);
ql_verify(ql, 1, 32, 32, 32);
quicklistDelRange(ql, 0, 32);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("delete range of entire node with overflow counts") { for (int f = optimize_start; f < 12; f++) {
quicklist *ql = quicklistCreate(); TEST_DESC("insert once in elements while iterating at fill %d at "
for (int i = 0; i < 32; i++) "compress %d\n",
quicklistPushHead(ql, F, genstr("hello", i), 32); f, options[_i]) {
ql_verify(ql, 1, 32, 32, 32); quicklist *ql = quicklistNew(f, options[_i]);
quicklistDelRange(ql, 0, 128); quicklistPushTail(ql, "abc", 3);
ql_verify(ql, 0, 0, 0, 0); quicklistSetFill(ql, 1);
quicklistRelease(ql); quicklistPushTail(ql, "def", 3); /* force to unique node */
} quicklistSetFill(ql, f);
quicklistPushTail(ql, "bob", 3); /* force to reset for +3 */
quicklistPushTail(ql, "foo", 3);
quicklistPushTail(ql, "zoo", 3);
itrprintr(ql, 0);
/* insert "bar" before "bob" while iterating over list. */
quicklistIter *iter = quicklistGetIterator(ql, AL_START_HEAD);
quicklistEntry entry;
while (quicklistNext(iter, &entry)) {
if (!strncmp((char *)entry.value, "bob", 3)) {
/* Insert as fill = 1 so it spills into new node. */
quicklistInsertBefore(ql, &entry, "bar", 3);
break; /* didn't we fix insert-while-iterating? */
}
}
itrprintr(ql, 0);
/* verify results */
quicklistIndex(ql, 0, &entry);
if (strncmp((char *)entry.value, "abc", 3))
ERR("Value 0 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistIndex(ql, 1, &entry);
if (strncmp((char *)entry.value, "def", 3))
ERR("Value 1 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistIndex(ql, 2, &entry);
if (strncmp((char *)entry.value, "bar", 3))
ERR("Value 2 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistIndex(ql, 3, &entry);
if (strncmp((char *)entry.value, "bob", 3))
ERR("Value 3 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistIndex(ql, 4, &entry);
if (strncmp((char *)entry.value, "foo", 3))
ERR("Value 4 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistIndex(ql, 5, &entry);
if (strncmp((char *)entry.value, "zoo", 3))
ERR("Value 5 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistReleaseIterator(iter);
quicklistRelease(ql);
}
}
TEST("delete middle 100 of 500 list") { for (int f = optimize_start; f < 1024; f++) {
quicklist *ql = quicklistCreate(); TEST_DESC(
for (int i = 0; i < 500; i++) "insert [before] 250 new in middle of 500 elements at fill"
quicklistPushTail(ql, F, genstr("hello", i + 1), 32); " %d at compress %d",
ql_verify(ql, 16, 500, 32, 20); f, options[_i]) {
quicklistDelRange(ql, 200, 100); quicklist *ql = quicklistNew(f, options[_i]);
ql_verify(ql, 14, 400, 32, 20); for (int i = 0; i < 500; i++)
quicklistRelease(ql); quicklistPushTail(ql, genstr("hello", i), 32);
} for (int i = 0; i < 250; i++) {
quicklistEntry entry;
quicklistIndex(ql, 250, &entry);
quicklistInsertBefore(ql, &entry, genstr("abc", i), 32);
}
if (f == 32)
ql_verify(ql, 25, 750, 32, 20);
quicklistRelease(ql);
}
}
TEST("delete negative 1 from 500 list") { for (int f = optimize_start; f < 1024; f++) {
quicklist *ql = quicklistCreate(); TEST_DESC("insert [after] 250 new in middle of 500 elements at "
for (int i = 0; i < 500; i++) "fill %d at compress %d",
quicklistPushTail(ql, F, genstr("hello", i + 1), 32); f, options[_i]) {
ql_verify(ql, 16, 500, 32, 20); quicklist *ql = quicklistNew(f, options[_i]);
quicklistDelRange(ql, -1, 1); for (int i = 0; i < 500; i++)
ql_verify(ql, 16, 499, 32, 19); quicklistPushHead(ql, genstr("hello", i), 32);
quicklistRelease(ql); for (int i = 0; i < 250; i++) {
} quicklistEntry entry;
quicklistIndex(ql, 250, &entry);
quicklistInsertAfter(ql, &entry, genstr("abc", i), 32);
}
TEST("delete negative 1 from 500 list with overflow counts") { if (ql->count != 750)
quicklist *ql = quicklistCreate(); ERR("List size not 750, but rather %ld", ql->count);
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, F, genstr("hello", i + 1), 32);
ql_verify(ql, 16, 500, 32, 20);
quicklistDelRange(ql, -1, 128);
ql_verify(ql, 16, 499, 32, 19);
quicklistRelease(ql);
}
TEST("delete negative 100 from 500 list") { if (f == 32)
quicklist *ql = quicklistCreate(); ql_verify(ql, 26, 750, 20, 32);
for (int i = 0; i < 500; i++) quicklistRelease(ql);
quicklistPushTail(ql, F, genstr("hello", i + 1), 32); }
quicklistDelRange(ql, -100, 100); }
ql_verify(ql, 13, 400, 32, 16);
quicklistRelease(ql);
}
TEST("delete -10 count 5 from 50 list") { TEST("duplicate empty list") {
quicklist *ql = quicklistCreate(); quicklist *ql = quicklistNew(-2, options[_i]);
for (int i = 0; i < 50; i++) ql_verify(ql, 0, 0, 0, 0);
quicklistPushTail(ql, F, genstr("hello", i + 1), 32); quicklist *copy = quicklistDup(ql);
ql_verify(ql, 2, 50, 32, 18); ql_verify(copy, 0, 0, 0, 0);
quicklistDelRange(ql, -10, 5); quicklistRelease(ql);
ql_verify(ql, 2, 45, 32, 13); quicklistRelease(copy);
quicklistRelease(ql); }
}
TEST("numbers only list read") { TEST("duplicate list of 1 element") {
quicklist *ql = quicklistCreate(); quicklist *ql = quicklistNew(-2, options[_i]);
quicklistPushTail(ql, F, "1111", 4); quicklistPushHead(ql, genstr("hello", 3), 32);
quicklistPushTail(ql, F, "2222", 4); ql_verify(ql, 1, 1, 1, 1);
quicklistPushTail(ql, F, "3333", 4); quicklist *copy = quicklistDup(ql);
quicklistPushTail(ql, F, "4444", 4); ql_verify(copy, 1, 1, 1, 1);
ql_verify(ql, 1, 4, 4, 4); quicklistRelease(ql);
quicklistEntry entry; quicklistRelease(copy);
quicklistIndex(ql, 0, &entry); }
if (entry.longval != 1111)
ERR("Not 1111, %lld", entry.longval);
quicklistIndex(ql, 1, &entry);
if (entry.longval != 2222)
ERR("Not 2222, %lld", entry.longval);
quicklistIndex(ql, 2, &entry);
if (entry.longval != 3333)
ERR("Not 3333, %lld", entry.longval);
quicklistIndex(ql, 3, &entry);
if (entry.longval != 4444)
ERR("Not 4444, %lld", entry.longval);
if (quicklistIndex(ql, 4, &entry))
ERR("Index past elements: %lld", entry.longval);
quicklistIndex(ql, -1, &entry);
if (entry.longval != 4444)
ERR("Not 4444 (reverse), %lld", entry.longval);
quicklistIndex(ql, -2, &entry);
if (entry.longval != 3333)
ERR("Not 3333 (reverse), %lld", entry.longval);
quicklistIndex(ql, -3, &entry);
if (entry.longval != 2222)
ERR("Not 2222 (reverse), %lld", entry.longval);
quicklistIndex(ql, -4, &entry);
if (entry.longval != 1111)
ERR("Not 1111 (reverse), %lld", entry.longval);
if (quicklistIndex(ql, -5, &entry))
ERR("Index past elements (reverse), %lld", entry.longval);
quicklistRelease(ql);
}
TEST("numbers larger list read") { TEST("duplicate list of 500") {
quicklist *ql = quicklistCreate(); quicklist *ql = quicklistNew(-2, options[_i]);
char num[32]; quicklistSetFill(ql, 32);
long long nums[5000]; for (int i = 0; i < 500; i++)
for (int i = 0; i < 5000; i++) { quicklistPushHead(ql, genstr("hello", i), 32);
nums[i] = -5157318210846258176 + i; ql_verify(ql, 16, 500, 20, 32);
int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, F, num, sz);
}
quicklistPushTail(ql, F, "xxxxxxxxxxxxxxxxxxxx", 20);
quicklistEntry entry;
for (int i = 0; i < 5000; i++) {
quicklistIndex(ql, i, &entry);
if (entry.longval != nums[i])
ERR("[%d] Not longval %lld but rather %lld", i, nums[i],
entry.longval);
entry.longval = 0xdeadbeef;
}
quicklistIndex(ql, 5000, &entry);
if (strncmp((char *)entry.value, "xxxxxxxxxxxxxxxxxxxx", 20))
ERR("String val not match: %s", entry.value);
ql_verify(ql, 157, 5001, 32, 9);
quicklistRelease(ql);
}
TEST("numbers larger list read B") { quicklist *copy = quicklistDup(ql);
quicklist *ql = quicklistCreate(); ql_verify(copy, 16, 500, 20, 32);
quicklistPushTail(ql, F, "99", 2); quicklistRelease(ql);
quicklistPushTail(ql, F, "98", 2); quicklistRelease(copy);
quicklistPushTail(ql, F, "xxxxxxxxxxxxxxxxxxxx", 20); }
quicklistPushTail(ql, F, "96", 2);
quicklistPushTail(ql, F, "95", 2);
quicklistReplaceAtIndex(ql, 1, "foo", 3);
quicklistReplaceAtIndex(ql, -1, "bar", 3);
quicklistRelease(ql);
OK;
}
for (int f = optimize_start; f < 16; f++) { for (int f = optimize_start; f < 512; f++) {
TEST_DESC("lrem test at fill %d", f) { TEST_DESC("index 1,200 from 500 list at fill %d at compress %d", f,
quicklist *ql = quicklistCreate(); options[_i]) {
char *words[] = { "abc", "foo", "bar", "foobar", "foobared", quicklist *ql = quicklistNew(f, options[_i]);
"zap", "bar", "test", "foo" }; for (int i = 0; i < 500; i++)
char *result[] = { "abc", "foo", "foobar", "foobared", quicklistPushTail(ql, genstr("hello", i + 1), 32);
"zap", "test", "foo" }; quicklistEntry entry;
char *resultB[] = { "abc", "foo", "foobar", quicklistIndex(ql, 1, &entry);
"foobared", "zap", "test" }; if (!strcmp((char *)entry.value, "hello2"))
for (int i = 0; i < 9; i++) OK;
quicklistPushTail(ql, f, words[i], strlen(words[i])); else
ERR("Value: %s", entry.value);
/* lrem 0 bar */ quicklistIndex(ql, 200, &entry);
quicklistIter *iter = quicklistGetIterator(ql, AL_START_HEAD); if (!strcmp((char *)entry.value, "hello201"))
quicklistEntry entry; OK;
int i = 0; else
while (quicklistNext(iter, &entry)) { ERR("Value: %s", entry.value);
if (quicklistCompare(entry.zi, (unsigned char *)"bar", 3)) { quicklistRelease(ql);
quicklistDelEntry(iter, &entry);
}
i++;
} }
quicklistReleaseIterator(iter);
/* check result of lrem 0 bar */ TEST_DESC("index -1,-2 from 500 list at fill %d at compress %d", f,
iter = quicklistGetIterator(ql, AL_START_HEAD); options[_i]) {
i = 0; quicklist *ql = quicklistNew(f, options[_i]);
int ok = 1; for (int i = 0; i < 500; i++)
while (quicklistNext(iter, &entry)) { quicklistPushTail(ql, genstr("hello", i + 1), 32);
/* Result must be: abc, foo, foobar, foobared, zap, test, foo */ quicklistEntry entry;
if (strncmp((char *)entry.value, result[i], entry.sz)) { quicklistIndex(ql, -1, &entry);
ERR("No match at position %d, got %.*s instead of %s", i, if (!strcmp((char *)entry.value, "hello500"))
entry.sz, entry.value, result[i]); OK;
ok = 0; else
} ERR("Value: %s", entry.value);
i++; quicklistIndex(ql, -2, &entry);
if (!strcmp((char *)entry.value, "hello499"))
OK;
else
ERR("Value: %s", entry.value);
quicklistRelease(ql);
} }
quicklistReleaseIterator(iter);
quicklistPushTail(ql, f, "foo", 3); TEST_DESC("index -100 from 500 list at fill %d at compress %d", f,
options[_i]) {
/* lrem -2 foo */ quicklist *ql = quicklistNew(f, options[_i]);
iter = quicklistGetIterator(ql, AL_START_TAIL); for (int i = 0; i < 500; i++)
i = 0; quicklistPushTail(ql, genstr("hello", i + 1), 32);
int del = 2; quicklistEntry entry;
while (quicklistNext(iter, &entry)) { quicklistIndex(ql, -100, &entry);
if (quicklistCompare(entry.zi, (unsigned char *)"foo", 3)) { if (!strcmp((char *)entry.value, "hello401"))
quicklistDelEntry(iter, &entry); OK;
del--; else
} ERR("Value: %s", entry.value);
if (!del) quicklistRelease(ql);
break;
i++;
} }
quicklistReleaseIterator(iter);
/* check result of lrem -2 foo */ TEST_DESC("index too big +1 from 50 list at fill %d at compress %d",
/* (we're ignoring the '2' part and still deleting all foo because f, options[_i]) {
* we only have two foo) */ quicklist *ql = quicklistNew(f, options[_i]);
iter = quicklistGetIterator(ql, AL_START_TAIL); for (int i = 0; i < 50; i++)
i = 0; quicklistPushTail(ql, genstr("hello", i + 1), 32);
size_t resB = sizeof(resultB) / sizeof(*resultB); quicklistEntry entry;
while (quicklistNext(iter, &entry)) { if (quicklistIndex(ql, 50, &entry))
/* Result must be: abc, foo, foobar, foobared, zap, test, foo */ ERR("Index found at 50 with 50 list: %.*s", entry.sz,
if (strncmp((char *)entry.value, resultB[resB - 1 - i], entry.value);
entry.sz)) { else
ERR("No match at position %d, got %.*s instead of %s", i, OK;
entry.sz, entry.value, resultB[resB - 1 - i]); quicklistRelease(ql);
ok = 0;
}
i++;
} }
}
quicklistReleaseIterator(iter); TEST("delete range empty list") {
/* final result of all tests */ quicklist *ql = quicklistNew(-2, options[_i]);
if (ok) quicklistDelRange(ql, 5, 20);
OK; ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql); quicklistRelease(ql);
} }
}
for (int f = optimize_start; f < 16; f++) { TEST("delete range of entire node in list of one node") {
TEST_DESC("iterate reverse + delete at fill %d", f) { quicklist *ql = quicklistNew(-2, options[_i]);
quicklist *ql = quicklistCreate(); for (int i = 0; i < 32; i++)
quicklistPushTail(ql, f, "abc", 3); quicklistPushHead(ql, genstr("hello", i), 32);
quicklistPushTail(ql, f, "def", 3); ql_verify(ql, 1, 32, 32, 32);
quicklistPushTail(ql, f, "hij", 3); quicklistDelRange(ql, 0, 32);
quicklistPushTail(ql, f, "jkl", 3); ql_verify(ql, 0, 0, 0, 0);
quicklistPushTail(ql, f, "oop", 3); quicklistRelease(ql);
}
quicklistEntry entry; TEST("delete range of entire node with overflow counts") {
quicklistIter *iter = quicklistGetIterator(ql, AL_START_TAIL); quicklist *ql = quicklistNew(-2, options[_i]);
int i = 0; for (int i = 0; i < 32; i++)
while (quicklistNext(iter, &entry)) { quicklistPushHead(ql, genstr("hello", i), 32);
if (quicklistCompare(entry.zi, (unsigned char *)"hij", 3)) { ql_verify(ql, 1, 32, 32, 32);
quicklistDelEntry(iter, &entry); quicklistDelRange(ql, 0, 128);
} ql_verify(ql, 0, 0, 0, 0);
i++; quicklistRelease(ql);
} }
quicklistReleaseIterator(iter);
if (i != 5) TEST("delete middle 100 of 500 list") {
ERR("Didn't iterate 5 times, iterated %d times.", i); quicklist *ql = quicklistNew(-2, options[_i]);
quicklistSetFill(ql, 32);
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i + 1), 32);
ql_verify(ql, 16, 500, 32, 20);
quicklistDelRange(ql, 200, 100);
ql_verify(ql, 14, 400, 32, 20);
quicklistRelease(ql);
}
/* Check results after deletion of "hij" */ TEST("delete negative 1 from 500 list") {
iter = quicklistGetIterator(ql, AL_START_HEAD); quicklist *ql = quicklistNew(-2, options[_i]);
i = 0; quicklistSetFill(ql, 32);
char *vals[] = { "abc", "def", "jkl", "oop" }; for (int i = 0; i < 500; i++)
while (quicklistNext(iter, &entry)) { quicklistPushTail(ql, genstr("hello", i + 1), 32);
if (!quicklistCompare(entry.zi, (unsigned char *)vals[i], 3)) { ql_verify(ql, 16, 500, 32, 20);
ERR("Value at %d didn't match %s\n", i, vals[i]); quicklistDelRange(ql, -1, 1);
} ql_verify(ql, 16, 499, 32, 19);
i++;
}
quicklistReleaseIterator(iter);
quicklistRelease(ql); quicklistRelease(ql);
} }
}
for (int f = optimize_start; f < 800; f++) { TEST("delete negative 1 from 500 list with overflow counts") {
TEST_DESC("iterator at index test at fill %d", f) { quicklist *ql = quicklistNew(-2, options[_i]);
quicklist *ql = quicklistCreate(); quicklistSetFill(ql, 32);
char num[32]; for (int i = 0; i < 500; i++)
long long nums[5000]; quicklistPushTail(ql, genstr("hello", i + 1), 32);
for (int i = 0; i < 760; i++) { ql_verify(ql, 16, 500, 32, 20);
nums[i] = -5157318210846258176 + i; quicklistDelRange(ql, -1, 128);
int sz = ll2string(num, sizeof(num), nums[i]); ql_verify(ql, 16, 499, 32, 19);
quicklistPushTail(ql, f, num, sz); quicklistRelease(ql);
} }
TEST("delete negative 100 from 500 list") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistSetFill(ql, 32);
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i + 1), 32);
quicklistDelRange(ql, -100, 100);
ql_verify(ql, 13, 400, 32, 16);
quicklistRelease(ql);
}
TEST("delete -10 count 5 from 50 list") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistSetFill(ql, 32);
for (int i = 0; i < 50; i++)
quicklistPushTail(ql, genstr("hello", i + 1), 32);
ql_verify(ql, 2, 50, 32, 18);
quicklistDelRange(ql, -10, 5);
ql_verify(ql, 2, 45, 32, 13);
quicklistRelease(ql);
}
TEST("numbers only list read") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistPushTail(ql, "1111", 4);
quicklistPushTail(ql, "2222", 4);
quicklistPushTail(ql, "3333", 4);
quicklistPushTail(ql, "4444", 4);
ql_verify(ql, 1, 4, 4, 4);
quicklistEntry entry; quicklistEntry entry;
quicklistIter *iter = quicklistIndex(ql, 0, &entry);
quicklistGetIteratorAtIdx(ql, AL_START_HEAD, 437); if (entry.longval != 1111)
int i = 437; ERR("Not 1111, %lld", entry.longval);
while (quicklistNext(iter, &entry)) { quicklistIndex(ql, 1, &entry);
if (entry.longval != nums[i]) if (entry.longval != 2222)
ERR("Expected %lld, but got %lld", entry.longval, nums[i]); ERR("Not 2222, %lld", entry.longval);
i++; quicklistIndex(ql, 2, &entry);
} if (entry.longval != 3333)
quicklistReleaseIterator(iter); ERR("Not 3333, %lld", entry.longval);
quicklistIndex(ql, 3, &entry);
if (entry.longval != 4444)
ERR("Not 4444, %lld", entry.longval);
if (quicklistIndex(ql, 4, &entry))
ERR("Index past elements: %lld", entry.longval);
quicklistIndex(ql, -1, &entry);
if (entry.longval != 4444)
ERR("Not 4444 (reverse), %lld", entry.longval);
quicklistIndex(ql, -2, &entry);
if (entry.longval != 3333)
ERR("Not 3333 (reverse), %lld", entry.longval);
quicklistIndex(ql, -3, &entry);
if (entry.longval != 2222)
ERR("Not 2222 (reverse), %lld", entry.longval);
quicklistIndex(ql, -4, &entry);
if (entry.longval != 1111)
ERR("Not 1111 (reverse), %lld", entry.longval);
if (quicklistIndex(ql, -5, &entry))
ERR("Index past elements (reverse), %lld", entry.longval);
quicklistRelease(ql); quicklistRelease(ql);
} }
}
for (int f = optimize_start; f < 40; f++) { TEST("numbers larger list read") {
TEST_DESC("ltrim test A at fill %d", f) { quicklist *ql = quicklistNew(-2, options[_i]);
quicklist *ql = quicklistCreate(); quicklistSetFill(ql, 32);
char num[32]; char num[32];
long long nums[5000]; long long nums[5000];
for (int i = 0; i < 32; i++) { for (int i = 0; i < 5000; i++) {
nums[i] = -5157318210846258176 + i; nums[i] = -5157318210846258176 + i;
int sz = ll2string(num, sizeof(num), nums[i]); int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, f, num, sz); quicklistPushTail(ql, num, sz);
} }
if (f == 32) quicklistPushTail(ql, "xxxxxxxxxxxxxxxxxxxx", 20);
ql_verify(ql, 1, 32, 32, 32);
/* ltrim 25 53 (keep [25,32] inclusive = 7 remaining) */
quicklistDelRange(ql, 0, 25);
quicklistDelRange(ql, 0, 0);
quicklistEntry entry; quicklistEntry entry;
for (int i = 0; i < 7; i++) { for (int i = 0; i < 5000; i++) {
quicklistIndex(ql, i, &entry); quicklistIndex(ql, i, &entry);
if (entry.longval != nums[25 + i]) if (entry.longval != nums[i])
ERR("Deleted invalid range! Expected %lld but got %lld", ERR("[%d] Not longval %lld but rather %lld", i, nums[i],
entry.longval, nums[25 + i]); entry.longval);
entry.longval = 0xdeadbeef;
} }
if (f == 32) quicklistIndex(ql, 5000, &entry);
ql_verify(ql, 1, 7, 7, 7); if (strncmp((char *)entry.value, "xxxxxxxxxxxxxxxxxxxx", 20))
ERR("String val not match: %s", entry.value);
ql_verify(ql, 157, 5001, 32, 9);
quicklistRelease(ql); quicklistRelease(ql);
} }
}
for (int f = optimize_start; f < 40; f++) { TEST("numbers larger list read B") {
TEST_DESC("ltrim test B at fill %d", f) { quicklist *ql = quicklistNew(-2, options[_i]);
quicklist *ql = quicklistCreate(); quicklistPushTail(ql, "99", 2);
char num[32]; quicklistPushTail(ql, "98", 2);
long long nums[5000]; quicklistPushTail(ql, "xxxxxxxxxxxxxxxxxxxx", 20);
for (int i = 0; i < 33; i++) { quicklistPushTail(ql, "96", 2);
nums[i] = i; quicklistPushTail(ql, "95", 2);
int sz = ll2string(num, sizeof(num), nums[i]); quicklistReplaceAtIndex(ql, 1, "foo", 3);
quicklistPushTail(ql, f, num, sz); quicklistReplaceAtIndex(ql, -1, "bar", 3);
quicklistRelease(ql);
OK;
}
for (int f = optimize_start; f < 16; f++) {
TEST_DESC("lrem test at fill %d at compress %d", f, options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
char *words[] = { "abc", "foo", "bar", "foobar", "foobared",
"zap", "bar", "test", "foo" };
char *result[] = { "abc", "foo", "foobar", "foobared",
"zap", "test", "foo" };
char *resultB[] = { "abc", "foo", "foobar",
"foobared", "zap", "test" };
for (int i = 0; i < 9; i++)
quicklistPushTail(ql, words[i], strlen(words[i]));
/* lrem 0 bar */
quicklistIter *iter = quicklistGetIterator(ql, AL_START_HEAD);
quicklistEntry entry;
int i = 0;
while (quicklistNext(iter, &entry)) {
if (quicklistCompare(entry.zi, (unsigned char *)"bar", 3)) {
quicklistDelEntry(iter, &entry);
}
i++;
}
quicklistReleaseIterator(iter);
/* check result of lrem 0 bar */
iter = quicklistGetIterator(ql, AL_START_HEAD);
i = 0;
int ok = 1;
while (quicklistNext(iter, &entry)) {
/* Result must be: abc, foo, foobar, foobared, zap, test,
* foo */
if (strncmp((char *)entry.value, result[i], entry.sz)) {
ERR("No match at position %d, got %.*s instead of %s",
i, entry.sz, entry.value, result[i]);
ok = 0;
}
i++;
}
quicklistReleaseIterator(iter);
quicklistPushTail(ql, "foo", 3);
/* lrem -2 foo */
iter = quicklistGetIterator(ql, AL_START_TAIL);
i = 0;
int del = 2;
while (quicklistNext(iter, &entry)) {
if (quicklistCompare(entry.zi, (unsigned char *)"foo", 3)) {
quicklistDelEntry(iter, &entry);
del--;
}
if (!del)
break;
i++;
}
quicklistReleaseIterator(iter);
/* check result of lrem -2 foo */
/* (we're ignoring the '2' part and still deleting all foo
* because
* we only have two foo) */
iter = quicklistGetIterator(ql, AL_START_TAIL);
i = 0;
size_t resB = sizeof(resultB) / sizeof(*resultB);
while (quicklistNext(iter, &entry)) {
/* Result must be: abc, foo, foobar, foobared, zap, test,
* foo */
if (strncmp((char *)entry.value, resultB[resB - 1 - i],
entry.sz)) {
ERR("No match at position %d, got %.*s instead of %s",
i, entry.sz, entry.value, resultB[resB - 1 - i]);
ok = 0;
}
i++;
}
quicklistReleaseIterator(iter);
/* final result of all tests */
if (ok)
OK;
quicklistRelease(ql);
} }
if (f == 32) }
ql_verify(ql, 2, 33, 32, 1);
/* ltrim 5 16 (keep [5,16] inclusive = 12 remaining) */ for (int f = optimize_start; f < 16; f++) {
quicklistDelRange(ql, 0, 5); TEST_DESC("iterate reverse + delete at fill %d at compress %d", f,
quicklistDelRange(ql, -16, 16); options[_i]) {
if (f == 32) quicklist *ql = quicklistNew(f, options[_i]);
ql_verify(ql, 1, 12, 12, 12); quicklistPushTail(ql, "abc", 3);
quicklistEntry entry; quicklistPushTail(ql, "def", 3);
quicklistIndex(ql, 0, &entry); quicklistPushTail(ql, "hij", 3);
if (entry.longval != 5) quicklistPushTail(ql, "jkl", 3);
ERR("A: longval not 5, but %lld", entry.longval); quicklistPushTail(ql, "oop", 3);
else
OK; quicklistEntry entry;
quicklistIndex(ql, -1, &entry); quicklistIter *iter = quicklistGetIterator(ql, AL_START_TAIL);
if (entry.longval != 16) int i = 0;
ERR("B! got instead: %lld", entry.longval); while (quicklistNext(iter, &entry)) {
else if (quicklistCompare(entry.zi, (unsigned char *)"hij", 3)) {
OK; quicklistDelEntry(iter, &entry);
quicklistPushTail(ql, f, "bobobob", 7); }
quicklistIndex(ql, -1, &entry); i++;
if (strncmp((char *)entry.value, "bobobob", 7)) }
ERR("Tail doesn't match bobobob, it's %.*s instead", entry.sz, quicklistReleaseIterator(iter);
entry.value);
for (int i = 0; i < 12; i++) { if (i != 5)
quicklistIndex(ql, i, &entry); ERR("Didn't iterate 5 times, iterated %d times.", i);
if (entry.longval != nums[5 + i])
ERR("Deleted invalid range! Expected %lld but got %lld", /* Check results after deletion of "hij" */
entry.longval, nums[5 + i]); iter = quicklistGetIterator(ql, AL_START_HEAD);
i = 0;
char *vals[] = { "abc", "def", "jkl", "oop" };
while (quicklistNext(iter, &entry)) {
if (!quicklistCompare(entry.zi, (unsigned char *)vals[i],
3)) {
ERR("Value at %d didn't match %s\n", i, vals[i]);
}
i++;
}
quicklistReleaseIterator(iter);
quicklistRelease(ql);
} }
quicklistRelease(ql);
} }
}
for (int f = optimize_start; f < 40; f++) { for (int f = optimize_start; f < 800; f++) {
TEST_DESC("ltrim test C at fill %d", f) { TEST_DESC("iterator at index test at fill %d at compress %d", f,
quicklist *ql = quicklistCreate(); options[_i]) {
char num[32]; quicklist *ql = quicklistNew(f, options[_i]);
long long nums[5000]; char num[32];
for (int i = 0; i < 33; i++) { long long nums[5000];
nums[i] = -5157318210846258176 + i; for (int i = 0; i < 760; i++) {
int sz = ll2string(num, sizeof(num), nums[i]); nums[i] = -5157318210846258176 + i;
quicklistPushTail(ql, f, num, sz); int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, num, sz);
}
quicklistEntry entry;
quicklistIter *iter =
quicklistGetIteratorAtIdx(ql, AL_START_HEAD, 437);
int i = 437;
while (quicklistNext(iter, &entry)) {
if (entry.longval != nums[i])
ERR("Expected %lld, but got %lld", entry.longval,
nums[i]);
i++;
}
quicklistReleaseIterator(iter);
quicklistRelease(ql);
} }
if (f == 32)
ql_verify(ql, 2, 33, 32, 1);
/* ltrim 3 3 (keep [3,3] inclusive = 1 remaining) */
quicklistDelRange(ql, 0, 3);
quicklistDelRange(ql, -29, 4000); /* make sure not loop forever */
if (f == 32)
ql_verify(ql, 1, 1, 1, 1);
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
if (entry.longval != -5157318210846258173)
ERROR;
else
OK;
quicklistRelease(ql);
} }
}
for (int f = optimize_start; f < 40; f++) { for (int f = optimize_start; f < 40; f++) {
TEST_DESC("ltrim test D at fill %d", f) { TEST_DESC("ltrim test A at fill %d at compress %d", f,
quicklist *ql = quicklistCreate(); options[_i]) {
char num[32]; quicklist *ql = quicklistNew(f, options[_i]);
long long nums[5000]; char num[32];
for (int i = 0; i < 33; i++) { long long nums[5000];
nums[i] = -5157318210846258176 + i; for (int i = 0; i < 32; i++) {
int sz = ll2string(num, sizeof(num), nums[i]); nums[i] = -5157318210846258176 + i;
quicklistPushTail(ql, f, num, sz); int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, num, sz);
}
if (f == 32)
ql_verify(ql, 1, 32, 32, 32);
/* ltrim 25 53 (keep [25,32] inclusive = 7 remaining) */
quicklistDelRange(ql, 0, 25);
quicklistDelRange(ql, 0, 0);
quicklistEntry entry;
for (int i = 0; i < 7; i++) {
quicklistIndex(ql, i, &entry);
if (entry.longval != nums[25 + i])
ERR("Deleted invalid range! Expected %lld but got "
"%lld",
entry.longval, nums[25 + i]);
}
if (f == 32)
ql_verify(ql, 1, 7, 7, 7);
quicklistRelease(ql);
} }
if (f == 32)
ql_verify(ql, 2, 33, 32, 1);
quicklistDelRange(ql, -12, 3);
if (ql->count != 30)
ERR("Didn't delete exactly three elements! Count is: %lu",
ql->count);
quicklistRelease(ql);
} }
}
for (int f = optimize_start; f < 72; f++) { for (int f = optimize_start; f < 40; f++) {
TEST_DESC("create quicklist from ziplist at fill %d", f) { TEST_DESC("ltrim test B at fill %d at compress %d", f,
unsigned char *zl = ziplistNew(); options[_i]) {
long long nums[32]; /* Force-disable compression because our 33 sequential
char num[32]; * integers don't compress and the check always fails. */
for (int i = 0; i < 33; i++) { quicklist *ql = quicklistNew(f, QUICKLIST_NOCOMPRESS);
nums[i] = -5157318210846258176 + i; char num[32];
int sz = ll2string(num, sizeof(num), nums[i]); long long nums[5000];
zl = ziplistPush(zl, (unsigned char *)num, sz, ZIPLIST_TAIL); for (int i = 0; i < 33; i++) {
nums[i] = i;
int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, num, sz);
}
if (f == 32)
ql_verify(ql, 2, 33, 32, 1);
/* ltrim 5 16 (keep [5,16] inclusive = 12 remaining) */
quicklistDelRange(ql, 0, 5);
quicklistDelRange(ql, -16, 16);
if (f == 32)
ql_verify(ql, 1, 12, 12, 12);
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
if (entry.longval != 5)
ERR("A: longval not 5, but %lld", entry.longval);
else
OK;
quicklistIndex(ql, -1, &entry);
if (entry.longval != 16)
ERR("B! got instead: %lld", entry.longval);
else
OK;
quicklistPushTail(ql, "bobobob", 7);
quicklistIndex(ql, -1, &entry);
if (strncmp((char *)entry.value, "bobobob", 7))
ERR("Tail doesn't match bobobob, it's %.*s instead",
entry.sz, entry.value);
for (int i = 0; i < 12; i++) {
quicklistIndex(ql, i, &entry);
if (entry.longval != nums[5 + i])
ERR("Deleted invalid range! Expected %lld but got "
"%lld",
entry.longval, nums[5 + i]);
}
quicklistRelease(ql);
} }
for (int i = 0; i < 33; i++) { }
zl = ziplistPush(zl, (unsigned char *)genstr("hello", i), 32,
ZIPLIST_TAIL); for (int f = optimize_start; f < 40; f++) {
TEST_DESC("ltrim test C at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
char num[32];
long long nums[5000];
for (int i = 0; i < 33; i++) {
nums[i] = -5157318210846258176 + i;
int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, num, sz);
}
if (f == 32)
ql_verify(ql, 2, 33, 32, 1);
/* ltrim 3 3 (keep [3,3] inclusive = 1 remaining) */
quicklistDelRange(ql, 0, 3);
quicklistDelRange(ql, -29,
4000); /* make sure not loop forever */
if (f == 32)
ql_verify(ql, 1, 1, 1, 1);
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
if (entry.longval != -5157318210846258173)
ERROR;
else
OK;
quicklistRelease(ql);
}
}
for (int f = optimize_start; f < 40; f++) {
TEST_DESC("ltrim test D at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
char num[32];
long long nums[5000];
for (int i = 0; i < 33; i++) {
nums[i] = -5157318210846258176 + i;
int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, num, sz);
}
if (f == 32)
ql_verify(ql, 2, 33, 32, 1);
quicklistDelRange(ql, -12, 3);
if (ql->count != 30)
ERR("Didn't delete exactly three elements! Count is: %lu",
ql->count);
quicklistRelease(ql);
}
}
for (int f = optimize_start; f < 72; f++) {
TEST_DESC("create quicklist from ziplist at fill %d at compress %d",
f, options[_i]) {
unsigned char *zl = ziplistNew();
long long nums[64];
char num[64];
for (int i = 0; i < 33; i++) {
nums[i] = -5157318210846258176 + i;
int sz = ll2string(num, sizeof(num), nums[i]);
zl =
ziplistPush(zl, (unsigned char *)num, sz, ZIPLIST_TAIL);
}
for (int i = 0; i < 33; i++) {
zl = ziplistPush(zl, (unsigned char *)genstr("hello", i),
32, ZIPLIST_TAIL);
}
quicklist *ql = quicklistCreateFromZiplist(f, options[_i], zl);
if (f == 1)
ql_verify(ql, 66, 66, 1, 1);
else if (f == 32)
ql_verify(ql, 3, 66, 32, 2);
else if (f == 66)
ql_verify(ql, 1, 66, 66, 66);
quicklistRelease(ql);
}
}
long long stop = mstime();
runtime[_i] = stop - start;
}
/* Run a longer test of compression depth outside of primary test loop. */
int list_sizes[] = { 250, 251, 500, 999, 1000 };
long long start = mstime();
for (int list = 0; list < (int)(sizeof(list_sizes) / sizeof(*list_sizes));
list++) {
for (int f = optimize_start; f < 128; f++) {
for (int depth = 1; depth < 40; depth++) {
/* skip over many redundant test cases */
TEST_DESC("verify specific compression of interior nodes with "
"%d list "
"at fill %d at compress %d",
list_sizes[list], f, depth) {
quicklist *ql = quicklistNew(f, depth);
for (int i = 0; i < list_sizes[list]; i++) {
quicklistPushTail(ql, genstr("hello TAIL", i + 1), 64);
quicklistPushHead(ql, genstr("hello HEAD", i + 1), 64);
}
quicklistNode *node = ql->head;
unsigned int low_raw = ql->compress;
unsigned int high_raw = ql->len - ql->compress;
for (unsigned int at = 0; at < ql->len;
at++, node = node->next) {
if (at < low_raw || at >= high_raw) {
if (node->encoding != QUICKLIST_NODE_ENCODING_RAW) {
ERR("Incorrect compression: node %d is "
"compressed at depth %d ((%u, %u); total "
"nodes: %u; size: %u)",
at, depth, low_raw, high_raw, ql->len,
node->sz);
}
} else {
if (node->encoding != QUICKLIST_NODE_ENCODING_LZF) {
ERR("Incorrect non-compression: node %d is NOT "
"compressed at depth %d ((%u, %u); total "
"nodes: %u; size: %u; attempted: %d)",
at, depth, low_raw, high_raw, ql->len,
node->sz, node->attempted_compress);
}
}
}
quicklistRelease(ql);
}
} }
quicklist *ql = quicklistCreateFromZiplist(f, zl);
if (f == 1)
ql_verify(ql, 66, 66, 1, 1);
else if (f == 32)
ql_verify(ql, 3, 66, 32, 2);
else if (f == 66)
ql_verify(ql, 1, 66, 66, 66);
quicklistRelease(ql);
} }
} }
long long stop = mstime();
printf("\n");
for (size_t i = 0; i < option_count; i++)
printf("Test Loop %02d: %0.2f seconds.\n", options[i],
(float)runtime[i] / 1000);
printf("Compressions: %0.2f seconds.\n", (float)(stop - start) / 1000);
printf("\n");
if (!err) if (!err)
printf("ALL TESTS PASSED!\n"); printf("ALL TESTS PASSED!\n");
......
...@@ -33,19 +33,50 @@ ...@@ -33,19 +33,50 @@
/* Node, quicklist, and Iterator are the only data structures used currently. */ /* Node, quicklist, and Iterator are the only data structures used currently. */
/* quicklistNode is a 32 byte struct describing a ziplist for a quicklist.
* We use bit fields keep the quicklistNode at 32 bytes.
* count: 16 bits, max 65536 (max zl bytes is 65k, so max count actually < 32k).
* encoding: 2 bits, RAW=1, LZF=2.
* container: 2 bits, NONE=1, ZIPLIST=2.
* recompress: 1 bit, bool, true if node is temporarry decompressed for usage.
* attempted_compress: 1 bit, boolean, used for verifying during testing.
* extra: 12 bits, free for future use; pads out the remainder of 32 bits */
typedef struct quicklistNode { typedef struct quicklistNode {
struct quicklistNode *prev; struct quicklistNode *prev;
struct quicklistNode *next; struct quicklistNode *next;
unsigned char *zl; unsigned char *zl;
unsigned int count; /* cached count of items in ziplist */ unsigned int sz; /* ziplist size in bytes */
unsigned int sz; /* ziplist size in bytes */ unsigned int count : 16; /* count of items in ziplist */
unsigned int encoding : 2; /* RAW==1 or LZF==2 */
unsigned int container : 2; /* NONE==1 or ZIPLIST==2 */
unsigned int recompress : 1; /* was this node previous compressed? */
unsigned int attempted_compress : 1; /* node can't compress; too small */
unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode; } quicklistNode;
/* quicklistLZF is a 4+N byte struct holding 'sz' followed by 'compressed'.
* 'sz' is byte length of 'compressed' field.
* 'compressed' is LZF data with total (compressed) length 'sz'
* NOTE: uncompressed length is stored in quicklistNode->sz.
* When quicklistNode->zl is compressed, node->zl points to a quicklistLZF */
typedef struct quicklistLZF {
unsigned int sz; /* LZF size in bytes*/
char compressed[];
} quicklistLZF;
/* quicklist is a 32 byte struct (on 64-bit systems) describing a quicklist.
* 'count' is the number of total entries.
* 'len' is the number of quicklist nodes.
* 'compress' is: -1 if compression disabled, otherwise it's the number
* of quicklistNodes to leave uncompressed at ends of quicklist.
* 'fill' is the user-requested (or default) fill factor. */
typedef struct quicklist { typedef struct quicklist {
quicklistNode *head; quicklistNode *head;
quicklistNode *tail; quicklistNode *tail;
unsigned long len; /* number of quicklistNodes */ unsigned long count; /* total count of all entries in all ziplists */
unsigned long count; /* total count of all entries in all ziplists */ unsigned int len; /* number of quicklistNodes */
int fill : 16; /* fill factor for individual nodes */
unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
} quicklist; } quicklist;
typedef struct quicklistIter { typedef struct quicklistIter {
...@@ -69,23 +100,40 @@ typedef struct quicklistEntry { ...@@ -69,23 +100,40 @@ typedef struct quicklistEntry {
#define QUICKLIST_HEAD 0 #define QUICKLIST_HEAD 0
#define QUICKLIST_TAIL -1 #define QUICKLIST_TAIL -1
/* quicklist node encodings */
#define QUICKLIST_NODE_ENCODING_RAW 1
#define QUICKLIST_NODE_ENCODING_LZF 2
/* quicklist compression disable */
#define QUICKLIST_NOCOMPRESS 0
/* quicklist container formats */
#define QUICKLIST_NODE_CONTAINER_NONE 1
#define QUICKLIST_NODE_CONTAINER_ZIPLIST 2
#define quicklistNodeIsCompressed(node) \
((node)->encoding == QUICKLIST_NODE_ENCODING_LZF)
/* Prototypes */ /* Prototypes */
quicklist *quicklistCreate(void); quicklist *quicklistCreate(void);
quicklist *quicklistNew(int fill, int compress);
void quicklistSetCompressDepth(quicklist *quicklist, int depth);
void quicklistSetFill(quicklist *quicklist, int fill);
void quicklistSetOptions(quicklist *quicklist, int fill, int depth);
void quicklistRelease(quicklist *quicklist); void quicklistRelease(quicklist *quicklist);
quicklist *quicklistPushHead(quicklist *quicklist, const int fill, void *value, int quicklistPushHead(quicklist *quicklist, void *value, const size_t sz);
const size_t sz); int quicklistPushTail(quicklist *quicklist, void *value, const size_t sz);
quicklist *quicklistPushTail(quicklist *quicklist, const int fill, void *value, void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
const size_t sz); int where);
void quicklistPush(quicklist *quicklist, const int fill, void *value,
const size_t sz, int where);
void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl); void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl);
quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist, quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist,
const int fill, unsigned char *zl); unsigned char *zl);
quicklist *quicklistCreateFromZiplist(int fill, unsigned char *zl); quicklist *quicklistCreateFromZiplist(int fill, int compress,
void quicklistInsertAfter(quicklist *quicklist, const int fill, unsigned char *zl);
quicklistEntry *node, void *value, const size_t sz); void quicklistInsertAfter(quicklist *quicklist, quicklistEntry *node,
void quicklistInsertBefore(quicklist *quicklist, const int fill, void *value, const size_t sz);
quicklistEntry *node, void *value, const size_t sz); void quicklistInsertBefore(quicklist *quicklist, quicklistEntry *node,
void *value, const size_t sz);
void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry); void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry);
int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data, int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data,
int sz); int sz);
...@@ -100,7 +148,7 @@ int quicklistIndex(const quicklist *quicklist, const long long index, ...@@ -100,7 +148,7 @@ int quicklistIndex(const quicklist *quicklist, const long long index,
quicklistEntry *entry); quicklistEntry *entry);
void quicklistRewind(quicklist *quicklist, quicklistIter *li); void quicklistRewind(quicklist *quicklist, quicklistIter *li);
void quicklistRewindTail(quicklist *quicklist, quicklistIter *li); void quicklistRewindTail(quicklist *quicklist, quicklistIter *li);
void quicklistRotate(quicklist *quicklist, const int fill); void quicklistRotate(quicklist *quicklist);
int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data, int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
unsigned int *sz, long long *sval, unsigned int *sz, long long *sval,
void *(*saver)(unsigned char *data, unsigned int sz)); void *(*saver)(unsigned char *data, unsigned int sz));
...@@ -108,6 +156,7 @@ int quicklistPop(quicklist *quicklist, int where, unsigned char **data, ...@@ -108,6 +156,7 @@ int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
unsigned int *sz, long long *slong); unsigned int *sz, long long *slong);
unsigned int quicklistCount(quicklist *ql); unsigned int quicklistCount(quicklist *ql);
int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len); int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len);
size_t quicklistGetLzf(const quicklistNode *node, void **data);
#ifdef REDIS_TEST #ifdef REDIS_TEST
int quicklistTest(int argc, char *argv[]); int quicklistTest(int argc, char *argv[]);
......
...@@ -209,43 +209,49 @@ int rdbTryIntegerEncoding(char *s, size_t len, unsigned char *enc) { ...@@ -209,43 +209,49 @@ int rdbTryIntegerEncoding(char *s, size_t len, unsigned char *enc) {
return rdbEncodeInteger(value,enc); return rdbEncodeInteger(value,enc);
} }
int rdbSaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) { int rdbSaveLzfBlob(rio *rdb, void *data, size_t compress_len,
size_t comprlen, outlen; size_t original_len) {
unsigned char byte; unsigned char byte;
int n, nwritten = 0; int n, nwritten = 0;
void *out;
/* We require at least four bytes compression for this to be worth it */
if (len <= 4) return 0;
outlen = len-4;
if ((out = zmalloc(outlen+1)) == NULL) return 0;
comprlen = lzf_compress(s, len, out, outlen);
if (comprlen == 0) {
zfree(out);
return 0;
}
/* Data compressed! Let's save it on disk */ /* Data compressed! Let's save it on disk */
byte = (REDIS_RDB_ENCVAL<<6)|REDIS_RDB_ENC_LZF; byte = (REDIS_RDB_ENCVAL<<6)|REDIS_RDB_ENC_LZF;
if ((n = rdbWriteRaw(rdb,&byte,1)) == -1) goto writeerr; if ((n = rdbWriteRaw(rdb,&byte,1)) == -1) goto writeerr;
nwritten += n; nwritten += n;
if ((n = rdbSaveLen(rdb,comprlen)) == -1) goto writeerr; if ((n = rdbSaveLen(rdb,compress_len)) == -1) goto writeerr;
nwritten += n; nwritten += n;
if ((n = rdbSaveLen(rdb,len)) == -1) goto writeerr; if ((n = rdbSaveLen(rdb,original_len)) == -1) goto writeerr;
nwritten += n; nwritten += n;
if ((n = rdbWriteRaw(rdb,out,comprlen)) == -1) goto writeerr; if ((n = rdbWriteRaw(rdb,data,compress_len)) == -1) goto writeerr;
nwritten += n; nwritten += n;
zfree(out);
return nwritten; return nwritten;
writeerr: writeerr:
zfree(out);
return -1; return -1;
} }
int rdbSaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) {
size_t comprlen, outlen;
void *out;
/* We require at least four bytes compression for this to be worth it */
if (len <= 4) return 0;
outlen = len-4;
if ((out = zmalloc(outlen+1)) == NULL) return 0;
comprlen = lzf_compress(s, len, out, outlen);
if (comprlen == 0) {
zfree(out);
return 0;
}
size_t nwritten = rdbSaveLzfBlob(rdb, out, comprlen, len);
zfree(out);
return nwritten;
}
robj *rdbLoadLzfStringObject(rio *rdb) { robj *rdbLoadLzfStringObject(rio *rdb) {
unsigned int len, clen; unsigned int len, clen;
unsigned char *c = NULL; unsigned char *c = NULL;
...@@ -491,8 +497,15 @@ int rdbSaveObject(rio *rdb, robj *o) { ...@@ -491,8 +497,15 @@ int rdbSaveObject(rio *rdb, robj *o) {
nwritten += n; nwritten += n;
do { do {
if ((n = rdbSaveRawString(rdb,node->zl,node->sz)) == -1) return -1; if (quicklistNodeIsCompressed(node)) {
nwritten += n; void *data;
size_t compress_len = quicklistGetLzf(node, &data);
if ((n = rdbSaveLzfBlob(rdb,data,compress_len,node->sz)) == -1) return -1;
nwritten += n;
} else {
if ((n = rdbSaveRawString(rdb,node->zl,node->sz)) == -1) return -1;
nwritten += n;
}
} while ((node = node->next)); } while ((node = node->next));
} else { } else {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
...@@ -822,14 +835,15 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) { ...@@ -822,14 +835,15 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
if ((len = rdbLoadLen(rdb,NULL)) == REDIS_RDB_LENERR) return NULL; if ((len = rdbLoadLen(rdb,NULL)) == REDIS_RDB_LENERR) return NULL;
o = createQuicklistObject(); o = createQuicklistObject();
quicklistSetFill(o->ptr, server.list_max_ziplist_entries);
quicklistSetCompress(o->ptr, 0 /*FIXME*/);
/* Load every single element of the list */ /* Load every single element of the list */
while(len--) { while(len--) {
if ((ele = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL; if ((ele = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL;
dec = getDecodedObject(ele); dec = getDecodedObject(ele);
size_t len = sdslen(dec->ptr); size_t len = sdslen(dec->ptr);
size_t zlen = server.list_max_ziplist_entries; o->ptr = quicklistPushTail(o->ptr, dec->ptr, len);
o->ptr = quicklistPushTail(o->ptr, zlen, dec->ptr, len);
decrRefCount(dec); decrRefCount(dec);
decrRefCount(ele); decrRefCount(ele);
} }
......
...@@ -43,10 +43,7 @@ void listTypePush(robj *subject, robj *value, int where) { ...@@ -43,10 +43,7 @@ void listTypePush(robj *subject, robj *value, int where) {
int pos = (where == REDIS_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL; int pos = (where == REDIS_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL;
value = getDecodedObject(value); value = getDecodedObject(value);
size_t len = sdslen(value->ptr); size_t len = sdslen(value->ptr);
size_t zlen = server.list_max_ziplist_entries; quicklistPush(subject->ptr, value->ptr, len, pos);
/* If this value is greater than our allowed values, create it in
* an isolated ziplist */
quicklistPush(subject->ptr, zlen, value->ptr, len, pos);
decrRefCount(value); decrRefCount(value);
} else { } else {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
...@@ -146,12 +143,11 @@ void listTypeInsert(listTypeEntry *entry, robj *value, int where) { ...@@ -146,12 +143,11 @@ void listTypeInsert(listTypeEntry *entry, robj *value, int where) {
value = getDecodedObject(value); value = getDecodedObject(value);
sds str = value->ptr; sds str = value->ptr;
size_t len = sdslen(str); size_t len = sdslen(str);
size_t zlen = server.list_max_ziplist_entries;
if (where == REDIS_TAIL) { if (where == REDIS_TAIL) {
quicklistInsertAfter((quicklist *)entry->entry.quicklist, zlen, quicklistInsertAfter((quicklist *)entry->entry.quicklist,
&entry->entry, str, len); &entry->entry, str, len);
} else if (where == REDIS_HEAD) { } else if (where == REDIS_HEAD) {
quicklistInsertBefore((quicklist *)entry->entry.quicklist, zlen, quicklistInsertBefore((quicklist *)entry->entry.quicklist,
&entry->entry, str, len); &entry->entry, str, len);
} }
decrRefCount(value); decrRefCount(value);
...@@ -188,7 +184,7 @@ void listTypeConvert(robj *subject, int enc) { ...@@ -188,7 +184,7 @@ void listTypeConvert(robj *subject, int enc) {
size_t zlen = server.list_max_ziplist_entries; size_t zlen = server.list_max_ziplist_entries;
subject->encoding = REDIS_ENCODING_QUICKLIST; subject->encoding = REDIS_ENCODING_QUICKLIST;
subject->ptr = quicklistCreateFromZiplist(zlen, subject->ptr); subject->ptr = quicklistCreateFromZiplist(zlen, 0 /*FIXME*/, subject->ptr);
} else { } else {
redisPanic("Unsupported list conversion"); redisPanic("Unsupported list conversion");
} }
...@@ -211,6 +207,8 @@ void pushGenericCommand(redisClient *c, int where) { ...@@ -211,6 +207,8 @@ void pushGenericCommand(redisClient *c, int where) {
c->argv[j] = tryObjectEncoding(c->argv[j]); c->argv[j] = tryObjectEncoding(c->argv[j]);
if (!lobj) { if (!lobj) {
lobj = createQuicklistObject(); lobj = createQuicklistObject();
quicklistSetFill(lobj->ptr, server.list_max_ziplist_entries);
quicklistSetCompress(lobj->ptr, 0 /*FIXME*/);
dbAdd(c->db,c->argv[1],lobj); dbAdd(c->db,c->argv[1],lobj);
} }
listTypePush(lobj,c->argv[j],where); listTypePush(lobj,c->argv[j],where);
...@@ -539,6 +537,8 @@ void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value ...@@ -539,6 +537,8 @@ void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value
/* Create the list if the key does not exist */ /* Create the list if the key does not exist */
if (!dstobj) { if (!dstobj) {
dstobj = createQuicklistObject(); dstobj = createQuicklistObject();
quicklistSetFill(dstobj->ptr, server.list_max_ziplist_entries);
quicklistSetCompress(dstobj->ptr, 0 /*FIXME*/);
dbAdd(c->db,dstkey,dstobj); dbAdd(c->db,dstkey,dstobj);
} }
signalModifiedKey(c->db,dstkey); signalModifiedKey(c->db,dstkey);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment