Commit 05ba119f authored by Salvatore Sanfilippo's avatar Salvatore Sanfilippo
Browse files

Merge pull request #2143 from mattsta/quicklist

Quicklist (linked list + ziplist)
parents 9e718a1f 5870e224
...@@ -58,7 +58,7 @@ ifeq ($(uname_S),SunOS) ...@@ -58,7 +58,7 @@ ifeq ($(uname_S),SunOS)
LUA_CFLAGS= -D__C99FEATURES__=1 LUA_CFLAGS= -D__C99FEATURES__=1
endif endif
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI -DENABLE_CJSON_GLOBAL $(CFLAGS) LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI -DENABLE_CJSON_GLOBAL -DREDIS_STATIC='' $(CFLAGS)
LUA_LDFLAGS+= $(LDFLAGS) LUA_LDFLAGS+= $(LDFLAGS)
# lua's Makefile defines AR="ar rcu", which is unusual, and makes it more # lua's Makefile defines AR="ar rcu", which is unusual, and makes it more
# challenging to cross-compile lua (and redis). These defines make it easier # challenging to cross-compile lua (and redis). These defines make it easier
......
...@@ -818,11 +818,36 @@ notify-keyspace-events "" ...@@ -818,11 +818,36 @@ notify-keyspace-events ""
hash-max-ziplist-entries 512 hash-max-ziplist-entries 512
hash-max-ziplist-value 64 hash-max-ziplist-value 64
# Similarly to hashes, small lists are also encoded in a special way in order # Lists are also encoded in a special way to save a lot of space.
# to save a lot of space. The special representation is only used when # The number of entries allowed per internal list node can be specified
# you are under the following limits: # as a fixed maximum size or a maximum number of elements.
list-max-ziplist-entries 512 # For a fixed maximum size, use -5 through -1, meaning:
list-max-ziplist-value 64 # -5: max size: 64 Kb <-- not recommended for normal workloads
# -4: max size: 32 Kb <-- not recommended
# -3: max size: 16 Kb <-- probably not recommended
# -2: max size: 8 Kb <-- good
# -1: max size: 4 Kb <-- good
# Positive numbers mean store up to _exactly_ that number of elements
# per list node.
# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size),
# but if your use case is unique, adjust the settings as necessary.
list-max-ziplist-size -2
# Lists may also be compressed.
# Compress depth is the number of quicklist ziplist nodes from *each* side of
# the list to *exclude* from compression. The head and tail of the list
# are always uncompressed for fast push/pop operations. Settings are:
# 0: disable all list compression
# 1: depth 1 means "don't start compressing until after 1 node into the list,
# going from either the head or tail"
# So: [head]->node->node->...->node->[tail]
# [head], [tail] will always be uncompressed; inner nodes will compress.
# 2: [head]->[next]->node->node->...->node->[prev]->[tail]
# 2 here means: don't compress head or head->next or tail->prev or tail,
# but compress all nodes between them.
# 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail]
# etc.
list-compress-depth 0
# Sets have a special encoding in just one case: when a set is composed # Sets have a special encoding in just one case: when a set is composed
# of just strings that happen to be integers in radix 10 in the range # of just strings that happen to be integers in radix 10 in the range
......
...@@ -18,7 +18,7 @@ OPTIMIZATION?=-O2 ...@@ -18,7 +18,7 @@ OPTIMIZATION?=-O2
DEPENDENCY_TARGETS=hiredis linenoise lua DEPENDENCY_TARGETS=hiredis linenoise lua
# Default settings # Default settings
STD=-std=c99 -pedantic STD=-std=c99 -pedantic -DREDIS_STATIC=''
WARN=-Wall -W WARN=-Wall -W
OPT=$(OPTIMIZATION) OPT=$(OPTIMIZATION)
...@@ -46,6 +46,10 @@ ifeq ($(USE_JEMALLOC),yes) ...@@ -46,6 +46,10 @@ ifeq ($(USE_JEMALLOC),yes)
MALLOC=jemalloc MALLOC=jemalloc
endif endif
ifeq ($(USE_JEMALLOC),no)
MALLOC=libc
endif
# Override default settings if possible # Override default settings if possible
-include .make-settings -include .make-settings
...@@ -113,7 +117,7 @@ endif ...@@ -113,7 +117,7 @@ endif
REDIS_SERVER_NAME=redis-server REDIS_SERVER_NAME=redis-server
REDIS_SENTINEL_NAME=redis-sentinel REDIS_SENTINEL_NAME=redis-sentinel
REDIS_SERVER_OBJ=adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o
REDIS_CLI_NAME=redis-cli REDIS_CLI_NAME=redis-cli
REDIS_CLI_OBJ=anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o REDIS_CLI_OBJ=anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o
REDIS_BENCHMARK_NAME=redis-benchmark REDIS_BENCHMARK_NAME=redis-benchmark
......
...@@ -770,52 +770,29 @@ int rioWriteBulkObject(rio *r, robj *obj) { ...@@ -770,52 +770,29 @@ int rioWriteBulkObject(rio *r, robj *obj) {
int rewriteListObject(rio *r, robj *key, robj *o) { int rewriteListObject(rio *r, robj *key, robj *o) {
long long count = 0, items = listTypeLength(o); long long count = 0, items = listTypeLength(o);
if (o->encoding == REDIS_ENCODING_ZIPLIST) { if (o->encoding == REDIS_ENCODING_QUICKLIST) {
unsigned char *zl = o->ptr; quicklist *list = o->ptr;
unsigned char *p = ziplistIndex(zl,0); quicklistIter *li = quicklistGetIterator(list, AL_START_HEAD);
unsigned char *vstr; quicklistEntry entry;
unsigned int vlen;
long long vlong;
while(ziplistGet(p,&vstr,&vlen,&vlong)) { while (quicklistNext(li,&entry)) {
if (count == 0) { if (count == 0) {
int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ? int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ?
REDIS_AOF_REWRITE_ITEMS_PER_CMD : items; REDIS_AOF_REWRITE_ITEMS_PER_CMD : items;
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0; if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0; if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0; if (rioWriteBulkObject(r,key) == 0) return 0;
} }
if (vstr) {
if (rioWriteBulkString(r,(char*)vstr,vlen) == 0) return 0;
} else {
if (rioWriteBulkLongLong(r,vlong) == 0) return 0;
}
p = ziplistNext(zl,p);
if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0;
items--;
}
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
list *list = o->ptr;
listNode *ln;
listIter li;
listRewind(list,&li); if (entry.value) {
while((ln = listNext(&li))) { if (rioWriteBulkString(r,(char*)entry.value,entry.sz) == 0) return 0;
robj *eleobj = listNodeValue(ln); } else {
if (rioWriteBulkLongLong(r,entry.longval) == 0) return 0;
if (count == 0) {
int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ?
REDIS_AOF_REWRITE_ITEMS_PER_CMD : items;
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0;
} }
if (rioWriteBulkObject(r,eleobj) == 0) return 0;
if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0; if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0;
items--; items--;
} }
quicklistReleaseIterator(li);
} else { } else {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
} }
......
...@@ -3886,10 +3886,7 @@ void clusterCommand(redisClient *c) { ...@@ -3886,10 +3886,7 @@ void clusterCommand(redisClient *c) {
server.cluster->stats_bus_messages_sent, server.cluster->stats_bus_messages_sent,
server.cluster->stats_bus_messages_received server.cluster->stats_bus_messages_received
); );
addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n", addReplyBulkSds(c, info);
(unsigned long)sdslen(info)));
addReplySds(c,info);
addReply(c,shared.crlf);
} else if (!strcasecmp(c->argv[1]->ptr,"saveconfig") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"saveconfig") && c->argc == 2) {
int retval = clusterSaveConfig(1); int retval = clusterSaveConfig(1);
......
...@@ -397,9 +397,13 @@ void loadServerConfigFromString(char *config) { ...@@ -397,9 +397,13 @@ void loadServerConfigFromString(char *config) {
} else if (!strcasecmp(argv[0],"hash-max-ziplist-value") && argc == 2) { } else if (!strcasecmp(argv[0],"hash-max-ziplist-value") && argc == 2) {
server.hash_max_ziplist_value = memtoll(argv[1], NULL); server.hash_max_ziplist_value = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){ } else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
server.list_max_ziplist_entries = memtoll(argv[1], NULL); /* DEAD OPTION */
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) { } else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
server.list_max_ziplist_value = memtoll(argv[1], NULL); /* DEAD OPTION */
} else if (!strcasecmp(argv[0],"list-max-ziplist-size") && argc == 2) {
server.list_max_ziplist_size = atoi(argv[1]);
} else if (!strcasecmp(argv[0],"list-compress-depth") && argc == 2) {
server.list_compress_depth = atoi(argv[1]);
} else if (!strcasecmp(argv[0],"set-max-intset-entries") && argc == 2) { } else if (!strcasecmp(argv[0],"set-max-intset-entries") && argc == 2) {
server.set_max_intset_entries = memtoll(argv[1], NULL); server.set_max_intset_entries = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"zset-max-ziplist-entries") && argc == 2) { } else if (!strcasecmp(argv[0],"zset-max-ziplist-entries") && argc == 2) {
...@@ -795,12 +799,12 @@ void configSetCommand(redisClient *c) { ...@@ -795,12 +799,12 @@ void configSetCommand(redisClient *c) {
} else if (!strcasecmp(c->argv[2]->ptr,"hash-max-ziplist-value")) { } else if (!strcasecmp(c->argv[2]->ptr,"hash-max-ziplist-value")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt; if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.hash_max_ziplist_value = ll; server.hash_max_ziplist_value = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"list-max-ziplist-entries")) { } else if (!strcasecmp(c->argv[2]->ptr,"list-max-ziplist-size")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt; if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.list_max_ziplist_entries = ll; server.list_max_ziplist_size = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"list-max-ziplist-value")) { } else if (!strcasecmp(c->argv[2]->ptr,"list-compress-depth")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt; if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.list_max_ziplist_value = ll; server.list_compress_depth = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"set-max-intset-entries")) { } else if (!strcasecmp(c->argv[2]->ptr,"set-max-intset-entries")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt; if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.set_max_intset_entries = ll; server.set_max_intset_entries = ll;
...@@ -1047,10 +1051,10 @@ void configGetCommand(redisClient *c) { ...@@ -1047,10 +1051,10 @@ void configGetCommand(redisClient *c) {
server.hash_max_ziplist_entries); server.hash_max_ziplist_entries);
config_get_numerical_field("hash-max-ziplist-value", config_get_numerical_field("hash-max-ziplist-value",
server.hash_max_ziplist_value); server.hash_max_ziplist_value);
config_get_numerical_field("list-max-ziplist-entries", config_get_numerical_field("list-max-ziplist-size",
server.list_max_ziplist_entries); server.list_max_ziplist_size);
config_get_numerical_field("list-max-ziplist-value", config_get_numerical_field("list-compress-depth",
server.list_max_ziplist_value); server.list_compress_depth);
config_get_numerical_field("set-max-intset-entries", config_get_numerical_field("set-max-intset-entries",
server.set_max_intset_entries); server.set_max_intset_entries);
config_get_numerical_field("zset-max-ziplist-entries", config_get_numerical_field("zset-max-ziplist-entries",
...@@ -1857,8 +1861,8 @@ int rewriteConfig(char *path) { ...@@ -1857,8 +1861,8 @@ int rewriteConfig(char *path) {
rewriteConfigNotifykeyspaceeventsOption(state); rewriteConfigNotifykeyspaceeventsOption(state);
rewriteConfigNumericalOption(state,"hash-max-ziplist-entries",server.hash_max_ziplist_entries,REDIS_HASH_MAX_ZIPLIST_ENTRIES); rewriteConfigNumericalOption(state,"hash-max-ziplist-entries",server.hash_max_ziplist_entries,REDIS_HASH_MAX_ZIPLIST_ENTRIES);
rewriteConfigNumericalOption(state,"hash-max-ziplist-value",server.hash_max_ziplist_value,REDIS_HASH_MAX_ZIPLIST_VALUE); rewriteConfigNumericalOption(state,"hash-max-ziplist-value",server.hash_max_ziplist_value,REDIS_HASH_MAX_ZIPLIST_VALUE);
rewriteConfigNumericalOption(state,"list-max-ziplist-entries",server.list_max_ziplist_entries,REDIS_LIST_MAX_ZIPLIST_ENTRIES); rewriteConfigNumericalOption(state,"list-max-ziplist-size",server.list_max_ziplist_size,REDIS_LIST_MAX_ZIPLIST_SIZE);
rewriteConfigNumericalOption(state,"list-max-ziplist-value",server.list_max_ziplist_value,REDIS_LIST_MAX_ZIPLIST_VALUE); rewriteConfigNumericalOption(state,"list-compress-depth",server.list_compress_depth,REDIS_LIST_COMPRESS_DEPTH);
rewriteConfigNumericalOption(state,"set-max-intset-entries",server.set_max_intset_entries,REDIS_SET_MAX_INTSET_ENTRIES); rewriteConfigNumericalOption(state,"set-max-intset-entries",server.set_max_intset_entries,REDIS_SET_MAX_INTSET_ENTRIES);
rewriteConfigNumericalOption(state,"zset-max-ziplist-entries",server.zset_max_ziplist_entries,REDIS_ZSET_MAX_ZIPLIST_ENTRIES); rewriteConfigNumericalOption(state,"zset-max-ziplist-entries",server.zset_max_ziplist_entries,REDIS_ZSET_MAX_ZIPLIST_ENTRIES);
rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,REDIS_ZSET_MAX_ZIPLIST_VALUE); rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,REDIS_ZSET_MAX_ZIPLIST_VALUE);
......
...@@ -181,9 +181,13 @@ uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) { ...@@ -181,9 +181,13 @@ uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) {
} }
/* Test main */ /* Test main */
#ifdef TEST_MAIN #ifdef REDIS_TEST
#include <stdio.h> #include <stdio.h>
int main(void) {
#define UNUSED(x) (void)(x)
int crc64Test(int argc, char *argv[]) {
UNUSED(argc);
UNUSED(argv);
printf("e9c6d914c4b8d9ca == %016llx\n", printf("e9c6d914c4b8d9ca == %016llx\n",
(unsigned long long) crc64(0,(unsigned char*)"123456789",9)); (unsigned long long) crc64(0,(unsigned char*)"123456789",9));
return 0; return 0;
......
...@@ -5,4 +5,8 @@ ...@@ -5,4 +5,8 @@
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l); uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
#ifdef REDIS_TEST
int crc64Test(int argc, char *argv[]);
#endif
#endif #endif
...@@ -252,6 +252,12 @@ void computeDatasetDigest(unsigned char *final) { ...@@ -252,6 +252,12 @@ void computeDatasetDigest(unsigned char *final) {
} }
} }
void inputCatSds(void *result, const char *str) {
/* result is actually a (sds *), so re-cast it here */
sds *info = (sds *)result;
*info = sdscat(*info, str);
}
void debugCommand(redisClient *c) { void debugCommand(redisClient *c) {
if (!strcasecmp(c->argv[1]->ptr,"segfault")) { if (!strcasecmp(c->argv[1]->ptr,"segfault")) {
*((char*)-1) = 'x'; *((char*)-1) = 'x';
...@@ -295,13 +301,46 @@ void debugCommand(redisClient *c) { ...@@ -295,13 +301,46 @@ void debugCommand(redisClient *c) {
val = dictGetVal(de); val = dictGetVal(de);
strenc = strEncoding(val->encoding); strenc = strEncoding(val->encoding);
char extra[128] = {0};
if (val->encoding == REDIS_ENCODING_QUICKLIST) {
char *nextra = extra;
int remaining = sizeof(extra);
quicklist *ql = val->ptr;
/* Add number of quicklist nodes */
int used = snprintf(nextra, remaining, " ql_nodes:%u", ql->len);
nextra += used;
remaining -= used;
/* Add average quicklist fill factor */
double avg = (double)ql->count/ql->len;
used = snprintf(nextra, remaining, " ql_avg_node:%.2f", avg);
nextra += used;
remaining -= used;
/* Add quicklist fill level / max ziplist size */
used = snprintf(nextra, remaining, " ql_ziplist_max:%d", ql->fill);
nextra += used;
remaining -= used;
/* Add isCompressed? */
int compressed = ql->compress != 0;
used = snprintf(nextra, remaining, " ql_compressed:%d", compressed);
nextra += used;
remaining -= used;
/* Add total uncompressed size */
unsigned long sz = 0;
for (quicklistNode *node = ql->head; node; node = node->next) {
sz += node->sz;
}
used = snprintf(nextra, remaining, " ql_uncompressed_size:%lu", sz);
nextra += used;
remaining -= used;
}
addReplyStatusFormat(c, addReplyStatusFormat(c,
"Value at:%p refcount:%d " "Value at:%p refcount:%d "
"encoding:%s serializedlength:%lld " "encoding:%s serializedlength:%lld "
"lru:%d lru_seconds_idle:%llu", "lru:%d lru_seconds_idle:%llu%s",
(void*)val, val->refcount, (void*)val, val->refcount,
strenc, (long long) rdbSavedObjectLen(val), strenc, (long long) rdbSavedObjectLen(val),
val->lru, estimateObjectIdleTime(val)/1000); val->lru, estimateObjectIdleTime(val)/1000, extra);
} else if (!strcasecmp(c->argv[1]->ptr,"sdslen") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"sdslen") && c->argc == 3) {
dictEntry *de; dictEntry *de;
robj *val; robj *val;
...@@ -379,6 +418,18 @@ void debugCommand(redisClient *c) { ...@@ -379,6 +418,18 @@ void debugCommand(redisClient *c) {
errstr = sdsmapchars(errstr,"\n\r"," ",2); /* no newlines in errors. */ errstr = sdsmapchars(errstr,"\n\r"," ",2); /* no newlines in errors. */
errstr = sdscatlen(errstr,"\r\n",2); errstr = sdscatlen(errstr,"\r\n",2);
addReplySds(c,errstr); addReplySds(c,errstr);
} else if (!strcasecmp(c->argv[1]->ptr,"jemalloc") && c->argc == 3) {
#if defined(USE_JEMALLOC)
if (!strcasecmp(c->argv[2]->ptr, "info")) {
sds info = sdsempty();
je_malloc_stats_print(inputCatSds, &info, NULL);
addReplyBulkSds(c, info);
} else {
addReplyErrorFormat(c, "Valid jemalloc debug fields: info");
}
#else
addReplyErrorFormat(c, "jemalloc support not available");
#endif
} else { } else {
addReplyErrorFormat(c, "Unknown DEBUG subcommand or wrong number of arguments for '%s'", addReplyErrorFormat(c, "Unknown DEBUG subcommand or wrong number of arguments for '%s'",
(char*)c->argv[1]->ptr); (char*)c->argv[1]->ptr);
......
...@@ -101,12 +101,16 @@ uint64_t intrev64(uint64_t v) { ...@@ -101,12 +101,16 @@ uint64_t intrev64(uint64_t v) {
return v; return v;
} }
#ifdef TESTMAIN #ifdef REDIS_TEST
#include <stdio.h> #include <stdio.h>
int main(void) { #define UNUSED(x) (void)(x)
int endianconvTest(int argc, char *argv[]) {
char buf[32]; char buf[32];
UNUSED(argc);
UNUSED(argv);
sprintf(buf,"ciaoroma"); sprintf(buf,"ciaoroma");
memrev16(buf); memrev16(buf);
printf("%s\n", buf); printf("%s\n", buf);
......
...@@ -71,4 +71,8 @@ uint64_t intrev64(uint64_t v); ...@@ -71,4 +71,8 @@ uint64_t intrev64(uint64_t v);
#define ntohu64(v) intrev64(v) #define ntohu64(v) intrev64(v)
#endif #endif
#ifdef REDIS_TEST
int endianconvTest(int argc, char *argv[]);
#endif
#endif #endif
...@@ -365,44 +365,46 @@ size_t intsetBlobLen(intset *is) { ...@@ -365,44 +365,46 @@ size_t intsetBlobLen(intset *is) {
return sizeof(intset)+intrev32ifbe(is->length)*intrev32ifbe(is->encoding); return sizeof(intset)+intrev32ifbe(is->length)*intrev32ifbe(is->encoding);
} }
#ifdef INTSET_TEST_MAIN #ifdef REDIS_TEST
#include <sys/time.h> #include <sys/time.h>
#include <time.h>
void intsetRepr(intset *is) { #if 0
int i; static void intsetRepr(intset *is) {
for (i = 0; i < intrev32ifbe(is->length); i++) { for (uint32_t i = 0; i < intrev32ifbe(is->length); i++) {
printf("%lld\n", (uint64_t)_intsetGet(is,i)); printf("%lld\n", (uint64_t)_intsetGet(is,i));
} }
printf("\n"); printf("\n");
} }
void error(char *err) { static void error(char *err) {
printf("%s\n", err); printf("%s\n", err);
exit(1); exit(1);
} }
#endif
void ok(void) { static void ok(void) {
printf("OK\n"); printf("OK\n");
} }
long long usec(void) { static long long usec(void) {
struct timeval tv; struct timeval tv;
gettimeofday(&tv,NULL); gettimeofday(&tv,NULL);
return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
} }
#define assert(_e) ((_e)?(void)0:(_assert(#_e,__FILE__,__LINE__),exit(1))) #define assert(_e) ((_e)?(void)0:(_assert(#_e,__FILE__,__LINE__),exit(1)))
void _assert(char *estr, char *file, int line) { static void _assert(char *estr, char *file, int line) {
printf("\n\n=== ASSERTION FAILED ===\n"); printf("\n\n=== ASSERTION FAILED ===\n");
printf("==> %s:%d '%s' is not true\n",file,line,estr); printf("==> %s:%d '%s' is not true\n",file,line,estr);
} }
intset *createSet(int bits, int size) { static intset *createSet(int bits, int size) {
uint64_t mask = (1<<bits)-1; uint64_t mask = (1<<bits)-1;
uint64_t i, value; uint64_t value;
intset *is = intsetNew(); intset *is = intsetNew();
for (i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
if (bits > 32) { if (bits > 32) {
value = (rand()*rand()) & mask; value = (rand()*rand()) & mask;
} else { } else {
...@@ -413,10 +415,8 @@ intset *createSet(int bits, int size) { ...@@ -413,10 +415,8 @@ intset *createSet(int bits, int size) {
return is; return is;
} }
void checkConsistency(intset *is) { static void checkConsistency(intset *is) {
int i; for (uint32_t i = 0; i < (intrev32ifbe(is->length)-1); i++) {
for (i = 0; i < (intrev32ifbe(is->length)-1); i++) {
uint32_t encoding = intrev32ifbe(is->encoding); uint32_t encoding = intrev32ifbe(is->encoding);
if (encoding == INTSET_ENC_INT16) { if (encoding == INTSET_ENC_INT16) {
...@@ -432,11 +432,15 @@ void checkConsistency(intset *is) { ...@@ -432,11 +432,15 @@ void checkConsistency(intset *is) {
} }
} }
int main(int argc, char **argv) { #define UNUSED(x) (void)(x)
int intsetTest(int argc, char **argv) {
uint8_t success; uint8_t success;
int i; int i;
intset *is; intset *is;
sranddev(); srand(time(NULL));
UNUSED(argc);
UNUSED(argv);
printf("Value encodings: "); { printf("Value encodings: "); {
assert(_intsetValueEncoding(-32768) == INTSET_ENC_INT16); assert(_intsetValueEncoding(-32768) == INTSET_ENC_INT16);
...@@ -464,7 +468,7 @@ int main(int argc, char **argv) { ...@@ -464,7 +468,7 @@ int main(int argc, char **argv) {
} }
printf("Large number of random adds: "); { printf("Large number of random adds: "); {
int inserts = 0; uint32_t inserts = 0;
is = intsetNew(); is = intsetNew();
for (i = 0; i < 1024; i++) { for (i = 0; i < 1024; i++) {
is = intsetAdd(is,rand()%0x800,&success); is = intsetAdd(is,rand()%0x800,&success);
...@@ -566,5 +570,7 @@ int main(int argc, char **argv) { ...@@ -566,5 +570,7 @@ int main(int argc, char **argv) {
checkConsistency(is); checkConsistency(is);
ok(); ok();
} }
return 0;
} }
#endif #endif
...@@ -48,4 +48,8 @@ uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value); ...@@ -48,4 +48,8 @@ uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);
uint32_t intsetLen(intset *is); uint32_t intsetLen(intset *is);
size_t intsetBlobLen(intset *is); size_t intsetBlobLen(intset *is);
#ifdef REDIS_TEST
int intsetTest(int argc, char *argv[]);
#endif
#endif // __INTSET_H #endif // __INTSET_H
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
* the difference between 15 and 14 is very small * the difference between 15 and 14 is very small
* for small blocks (and 14 is usually a bit faster). * for small blocks (and 14 is usually a bit faster).
* For a low-memory/faster configuration, use HLOG == 13; * For a low-memory/faster configuration, use HLOG == 13;
* For best compression, use 15 or 16 (or more, up to 23). * For best compression, use 15 or 16 (or more, up to 22).
*/ */
#ifndef HLOG #ifndef HLOG
# define HLOG 16 # define HLOG 16
...@@ -94,7 +94,7 @@ ...@@ -94,7 +94,7 @@
/* /*
* Avoid assigning values to errno variable? for some embedding purposes * Avoid assigning values to errno variable? for some embedding purposes
* (linux kernel for example), this is necessary. NOTE: this breaks * (linux kernel for example), this is necessary. NOTE: this breaks
* the documentation in lzf.h. * the documentation in lzf.h. Avoiding errno has no speed impact.
*/ */
#ifndef AVOID_ERRNO #ifndef AVOID_ERRNO
# define AVOID_ERRNO 0 # define AVOID_ERRNO 0
...@@ -121,16 +121,52 @@ ...@@ -121,16 +121,52 @@
# define CHECK_INPUT 1 # define CHECK_INPUT 1
#endif #endif
/*
* Whether to store pointers or offsets inside the hash table. On
* 64 bit architetcures, pointers take up twice as much space,
* and might also be slower. Default is to autodetect.
*/
/*#define LZF_USER_OFFSETS autodetect */
/*****************************************************************************/ /*****************************************************************************/
/* nothing should be changed below */ /* nothing should be changed below */
#ifdef __cplusplus
# include <cstring>
# include <climits>
using namespace std;
#else
# include <string.h>
# include <limits.h>
#endif
#ifndef LZF_USE_OFFSETS
# if defined (WIN32)
# define LZF_USE_OFFSETS defined(_M_X64)
# else
# if __cplusplus > 199711L
# include <cstdint>
# else
# include <stdint.h>
# endif
# define LZF_USE_OFFSETS (UINTPTR_MAX > 0xffffffffU)
# endif
#endif
typedef unsigned char u8; typedef unsigned char u8;
typedef const u8 *LZF_STATE[1 << (HLOG)]; #if LZF_USE_OFFSETS
# define LZF_HSLOT_BIAS ((const u8 *)in_data)
typedef unsigned int LZF_HSLOT;
#else
# define LZF_HSLOT_BIAS 0
typedef const u8 *LZF_HSLOT;
#endif
typedef LZF_HSLOT LZF_STATE[1 << (HLOG)];
#if !STRICT_ALIGN #if !STRICT_ALIGN
/* for unaligned accesses we need a 16 bit datatype. */ /* for unaligned accesses we need a 16 bit datatype. */
# include <limits.h>
# if USHRT_MAX == 65535 # if USHRT_MAX == 65535
typedef unsigned short u16; typedef unsigned short u16;
# elif UINT_MAX == 65535 # elif UINT_MAX == 65535
...@@ -142,17 +178,7 @@ typedef const u8 *LZF_STATE[1 << (HLOG)]; ...@@ -142,17 +178,7 @@ typedef const u8 *LZF_STATE[1 << (HLOG)];
#endif #endif
#if ULTRA_FAST #if ULTRA_FAST
# if defined(VERY_FAST) # undef VERY_FAST
# undef VERY_FAST
# endif
#endif
#if INIT_HTAB
# ifdef __cplusplus
# include <cstring>
# else
# include <string.h>
# endif
#endif #endif
#endif #endif
......
/* /*
* Copyright (c) 2000-2008 Marc Alexander Lehmann <schmorp@schmorp.de> * Copyright (c) 2000-2010 Marc Alexander Lehmann <schmorp@schmorp.de>
* *
* Redistribution and use in source and binary forms, with or without modifica- * Redistribution and use in source and binary forms, with or without modifica-
* tion, are permitted provided that the following conditions are met: * tion, are permitted provided that the following conditions are met:
...@@ -40,8 +40,8 @@ ...@@ -40,8 +40,8 @@
/* /*
* don't play with this unless you benchmark! * don't play with this unless you benchmark!
* decompression is not dependent on the hash function * the data format is not dependent on the hash function.
* the hashing function might seem strange, just believe me * the hash function might seem strange, just believe me,
* it works ;) * it works ;)
*/ */
#ifndef FRST #ifndef FRST
...@@ -89,9 +89,9 @@ ...@@ -89,9 +89,9 @@
/* /*
* compressed format * compressed format
* *
* 000LLLLL <L+1> ; literal * 000LLLLL <L+1> ; literal, L+1=1..33 octets
* LLLooooo oooooooo ; backref L * LLLooooo oooooooo ; backref L+1=1..7 octets, o+1=1..4096 offset
* 111ooooo LLLLLLLL oooooooo ; backref L+7 * 111ooooo LLLLLLLL oooooooo ; backref L+8 octets, o+1=1..4096 offset
* *
*/ */
...@@ -106,7 +106,6 @@ lzf_compress (const void *const in_data, unsigned int in_len, ...@@ -106,7 +106,6 @@ lzf_compress (const void *const in_data, unsigned int in_len,
#if !LZF_STATE_ARG #if !LZF_STATE_ARG
LZF_STATE htab; LZF_STATE htab;
#endif #endif
const u8 **hslot;
const u8 *ip = (const u8 *)in_data; const u8 *ip = (const u8 *)in_data;
u8 *op = (u8 *)out_data; u8 *op = (u8 *)out_data;
const u8 *in_end = ip + in_len; const u8 *in_end = ip + in_len;
...@@ -133,10 +132,6 @@ lzf_compress (const void *const in_data, unsigned int in_len, ...@@ -133,10 +132,6 @@ lzf_compress (const void *const in_data, unsigned int in_len,
#if INIT_HTAB #if INIT_HTAB
memset (htab, 0, sizeof (htab)); memset (htab, 0, sizeof (htab));
# if 0
for (hslot = htab; hslot < htab + HSIZE; hslot++)
*hslot++ = ip;
# endif
#endif #endif
lit = 0; op++; /* start run */ lit = 0; op++; /* start run */
...@@ -144,24 +139,23 @@ lzf_compress (const void *const in_data, unsigned int in_len, ...@@ -144,24 +139,23 @@ lzf_compress (const void *const in_data, unsigned int in_len,
hval = FRST (ip); hval = FRST (ip);
while (ip < in_end - 2) while (ip < in_end - 2)
{ {
LZF_HSLOT *hslot;
hval = NEXT (hval, ip); hval = NEXT (hval, ip);
hslot = htab + IDX (hval); hslot = htab + IDX (hval);
ref = *hslot; *hslot = ip; ref = *hslot + LZF_HSLOT_BIAS; *hslot = ip - LZF_HSLOT_BIAS;
if (1 if (1
#if INIT_HTAB #if INIT_HTAB
&& ref < ip /* the next test will actually take care of this, but this is faster */ && ref < ip /* the next test will actually take care of this, but this is faster */
#endif #endif
&& (off = ip - ref - 1) < MAX_OFF && (off = ip - ref - 1) < MAX_OFF
&& ip + 4 < in_end
&& ref > (u8 *)in_data && ref > (u8 *)in_data
#if STRICT_ALIGN
&& ref[0] == ip[0]
&& ref[1] == ip[1]
&& ref[2] == ip[2] && ref[2] == ip[2]
#if STRICT_ALIGN
&& ((ref[1] << 8) | ref[0]) == ((ip[1] << 8) | ip[0])
#else #else
&& *(u16 *)ref == *(u16 *)ip && *(u16 *)ref == *(u16 *)ip
&& ref[2] == ip[2]
#endif #endif
) )
{ {
...@@ -170,12 +164,13 @@ lzf_compress (const void *const in_data, unsigned int in_len, ...@@ -170,12 +164,13 @@ lzf_compress (const void *const in_data, unsigned int in_len,
unsigned int maxlen = in_end - ip - len; unsigned int maxlen = in_end - ip - len;
maxlen = maxlen > MAX_REF ? MAX_REF : maxlen; maxlen = maxlen > MAX_REF ? MAX_REF : maxlen;
if (expect_false (op + 3 + 1 >= out_end)) /* first a faster conservative test */
if (op - !lit + 3 + 1 >= out_end) /* second the exact but rare test */
return 0;
op [- lit - 1] = lit - 1; /* stop run */ op [- lit - 1] = lit - 1; /* stop run */
op -= !lit; /* undo run if length is zero */ op -= !lit; /* undo run if length is zero */
if (expect_false (op + 3 + 1 >= out_end))
return 0;
for (;;) for (;;)
{ {
if (expect_true (maxlen > 16)) if (expect_true (maxlen > 16))
...@@ -222,6 +217,7 @@ lzf_compress (const void *const in_data, unsigned int in_len, ...@@ -222,6 +217,7 @@ lzf_compress (const void *const in_data, unsigned int in_len,
} }
*op++ = off; *op++ = off;
lit = 0; op++; /* start run */ lit = 0; op++; /* start run */
ip += len + 1; ip += len + 1;
...@@ -237,12 +233,12 @@ lzf_compress (const void *const in_data, unsigned int in_len, ...@@ -237,12 +233,12 @@ lzf_compress (const void *const in_data, unsigned int in_len,
hval = FRST (ip); hval = FRST (ip);
hval = NEXT (hval, ip); hval = NEXT (hval, ip);
htab[IDX (hval)] = ip; htab[IDX (hval)] = ip - LZF_HSLOT_BIAS;
ip++; ip++;
# if VERY_FAST && !ULTRA_FAST # if VERY_FAST && !ULTRA_FAST
hval = NEXT (hval, ip); hval = NEXT (hval, ip);
htab[IDX (hval)] = ip; htab[IDX (hval)] = ip - LZF_HSLOT_BIAS;
ip++; ip++;
# endif # endif
#else #else
...@@ -251,7 +247,7 @@ lzf_compress (const void *const in_data, unsigned int in_len, ...@@ -251,7 +247,7 @@ lzf_compress (const void *const in_data, unsigned int in_len,
do do
{ {
hval = NEXT (hval, ip); hval = NEXT (hval, ip);
htab[IDX (hval)] = ip; htab[IDX (hval)] = ip - LZF_HSLOT_BIAS;
ip++; ip++;
} }
while (len--); while (len--);
......
/* /*
* Copyright (c) 2000-2007 Marc Alexander Lehmann <schmorp@schmorp.de> * Copyright (c) 2000-2010 Marc Alexander Lehmann <schmorp@schmorp.de>
* *
* Redistribution and use in source and binary forms, with or without modifica- * Redistribution and use in source and binary forms, with or without modifica-
* tion, are permitted provided that the following conditions are met: * tion, are permitted provided that the following conditions are met:
...@@ -43,14 +43,14 @@ ...@@ -43,14 +43,14 @@
# define SET_ERRNO(n) errno = (n) # define SET_ERRNO(n) errno = (n)
#endif #endif
/* #if USE_REP_MOVSB /* small win on amd, big loss on intel */
#if (__i386 || __amd64) && __GNUC__ >= 3 #if (__i386 || __amd64) && __GNUC__ >= 3
# define lzf_movsb(dst, src, len) \ # define lzf_movsb(dst, src, len) \
asm ("rep movsb" \ asm ("rep movsb" \
: "=D" (dst), "=S" (src), "=c" (len) \ : "=D" (dst), "=S" (src), "=c" (len) \
: "0" (dst), "1" (src), "2" (len)); : "0" (dst), "1" (src), "2" (len));
#endif #endif
*/ #endif
unsigned int unsigned int
lzf_decompress (const void *const in_data, unsigned int in_len, lzf_decompress (const void *const in_data, unsigned int in_len,
...@@ -86,9 +86,17 @@ lzf_decompress (const void *const in_data, unsigned int in_len, ...@@ -86,9 +86,17 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
#ifdef lzf_movsb #ifdef lzf_movsb
lzf_movsb (op, ip, ctrl); lzf_movsb (op, ip, ctrl);
#else #else
do switch (ctrl)
*op++ = *ip++; {
while (--ctrl); case 32: *op++ = *ip++; case 31: *op++ = *ip++; case 30: *op++ = *ip++; case 29: *op++ = *ip++;
case 28: *op++ = *ip++; case 27: *op++ = *ip++; case 26: *op++ = *ip++; case 25: *op++ = *ip++;
case 24: *op++ = *ip++; case 23: *op++ = *ip++; case 22: *op++ = *ip++; case 21: *op++ = *ip++;
case 20: *op++ = *ip++; case 19: *op++ = *ip++; case 18: *op++ = *ip++; case 17: *op++ = *ip++;
case 16: *op++ = *ip++; case 15: *op++ = *ip++; case 14: *op++ = *ip++; case 13: *op++ = *ip++;
case 12: *op++ = *ip++; case 11: *op++ = *ip++; case 10: *op++ = *ip++; case 9: *op++ = *ip++;
case 8: *op++ = *ip++; case 7: *op++ = *ip++; case 6: *op++ = *ip++; case 5: *op++ = *ip++;
case 4: *op++ = *ip++; case 3: *op++ = *ip++; case 2: *op++ = *ip++; case 1: *op++ = *ip++;
}
#endif #endif
} }
else /* back reference */ else /* back reference */
...@@ -134,12 +142,39 @@ lzf_decompress (const void *const in_data, unsigned int in_len, ...@@ -134,12 +142,39 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
len += 2; len += 2;
lzf_movsb (op, ref, len); lzf_movsb (op, ref, len);
#else #else
*op++ = *ref++; switch (len)
*op++ = *ref++; {
default:
do len += 2;
*op++ = *ref++;
while (--len); if (op >= ref + len)
{
/* disjunct areas */
memcpy (op, ref, len);
op += len;
}
else
{
/* overlapping, use octte by octte copying */
do
*op++ = *ref++;
while (--len);
}
break;
case 9: *op++ = *ref++;
case 8: *op++ = *ref++;
case 7: *op++ = *ref++;
case 6: *op++ = *ref++;
case 5: *op++ = *ref++;
case 4: *op++ = *ref++;
case 3: *op++ = *ref++;
case 2: *op++ = *ref++;
case 1: *op++ = *ref++;
case 0: *op++ = *ref++; /* two octets more */
*op++ = *ref++;
}
#endif #endif
} }
} }
......
...@@ -525,6 +525,14 @@ void addReplyBulkCBuffer(redisClient *c, void *p, size_t len) { ...@@ -525,6 +525,14 @@ void addReplyBulkCBuffer(redisClient *c, void *p, size_t len) {
addReply(c,shared.crlf); addReply(c,shared.crlf);
} }
/* Add sds to reply (takes ownership of sds and frees it) */
void addReplyBulkSds(redisClient *c, sds s) {
addReplySds(c,sdscatfmt(sdsempty(),"$%u\r\n",
(unsigned long)sdslen(s)));
addReplySds(c,s);
addReply(c,shared.crlf);
}
/* Add a C nul term string as bulk reply */ /* Add a C nul term string as bulk reply */
void addReplyBulkCString(redisClient *c, char *s) { void addReplyBulkCString(redisClient *c, char *s) {
if (s == NULL) { if (s == NULL) {
......
...@@ -180,11 +180,10 @@ robj *dupStringObject(robj *o) { ...@@ -180,11 +180,10 @@ robj *dupStringObject(robj *o) {
} }
} }
robj *createListObject(void) { robj *createQuicklistObject(void) {
list *l = listCreate(); quicklist *l = quicklistCreate();
robj *o = createObject(REDIS_LIST,l); robj *o = createObject(REDIS_LIST,l);
listSetFreeMethod(l,decrRefCountVoid); o->encoding = REDIS_ENCODING_QUICKLIST;
o->encoding = REDIS_ENCODING_LINKEDLIST;
return o; return o;
} }
...@@ -242,11 +241,8 @@ void freeStringObject(robj *o) { ...@@ -242,11 +241,8 @@ void freeStringObject(robj *o) {
void freeListObject(robj *o) { void freeListObject(robj *o) {
switch (o->encoding) { switch (o->encoding) {
case REDIS_ENCODING_LINKEDLIST: case REDIS_ENCODING_QUICKLIST:
listRelease((list*) o->ptr); quicklistRelease(o->ptr);
break;
case REDIS_ENCODING_ZIPLIST:
zfree(o->ptr);
break; break;
default: default:
redisPanic("Unknown list encoding type"); redisPanic("Unknown list encoding type");
...@@ -678,7 +674,7 @@ char *strEncoding(int encoding) { ...@@ -678,7 +674,7 @@ char *strEncoding(int encoding) {
case REDIS_ENCODING_RAW: return "raw"; case REDIS_ENCODING_RAW: return "raw";
case REDIS_ENCODING_INT: return "int"; case REDIS_ENCODING_INT: return "int";
case REDIS_ENCODING_HT: return "hashtable"; case REDIS_ENCODING_HT: return "hashtable";
case REDIS_ENCODING_LINKEDLIST: return "linkedlist"; case REDIS_ENCODING_QUICKLIST: return "quicklist";
case REDIS_ENCODING_ZIPLIST: return "ziplist"; case REDIS_ENCODING_ZIPLIST: return "ziplist";
case REDIS_ENCODING_INTSET: return "intset"; case REDIS_ENCODING_INTSET: return "intset";
case REDIS_ENCODING_SKIPLIST: return "skiplist"; case REDIS_ENCODING_SKIPLIST: return "skiplist";
......
/* quicklist.c - A doubly linked list of ziplists
*
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must start the above copyright notice,
* this quicklist of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this quicklist of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <string.h> /* for memcpy */
#include "quicklist.h"
#include "zmalloc.h"
#include "ziplist.h"
#include "util.h" /* for ll2string */
#include "lzf.h"
#if defined(REDIS_TEST) || defined(REDIS_TEST_VERBOSE)
#include <stdio.h> /* for printf (debug printing), snprintf (genstr) */
#endif
#ifndef REDIS_STATIC
#define REDIS_STATIC static
#endif
/* Optimization levels for size-based filling */
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
/* 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. */
#ifndef REDIS_TEST_VERBOSE
#define D(...)
#else
#define D(...) \
do { \
printf("%s:%s:%d:\t", __FILE__, __FUNCTION__, __LINE__); \
printf(__VA_ARGS__); \
printf("\n"); \
} while (0);
#endif
/* Simple way to give quicklistEntry structs default values with one call. */
#define initEntry(e) \
do { \
(e)->zi = (e)->value = NULL; \
(e)->longval = -123456789; \
(e)->quicklist = NULL; \
(e)->node = NULL; \
(e)->offset = 123456789; \
(e)->sz = 0; \
} while (0)
#if __GNUC__ >= 3
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
#else
#define likely(x) (x)
#define unlikely(x) (x)
#endif
/* Create a new quicklist.
* Free with quicklistRelease(). */
quicklist *quicklistCreate(void) {
struct quicklist *quicklist;
quicklist = zmalloc(sizeof(*quicklist));
quicklist->head = quicklist->tail = NULL;
quicklist->len = 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;
}
REDIS_STATIC quicklistNode *quicklistCreateNode(void) {
quicklistNode *node;
node = zmalloc(sizeof(*node));
node->zl = NULL;
node->count = 0;
node->sz = 0;
node->next = node->prev = NULL;
node->encoding = QUICKLIST_NODE_ENCODING_RAW;
node->container = QUICKLIST_NODE_CONTAINER_ZIPLIST;
node->recompress = 0;
return node;
}
/* Return cached quicklist count */
unsigned int quicklistCount(quicklist *ql) { return ql->count; }
/* Free entire quicklist. */
void quicklistRelease(quicklist *quicklist) {
unsigned long len;
quicklistNode *current, *next;
current = quicklist->head;
len = quicklist->len;
while (len--) {
next = current->next;
zfree(current->zl);
quicklist->count -= current->count;
zfree(current);
quicklist->len--;
current = next;
}
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. */
REDIS_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. */
REDIS_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. */
REDIS_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' 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. */
REDIS_STATIC void __quicklistInsertNode(quicklist *quicklist,
quicklistNode *old_node,
quicklistNode *new_node, int after) {
if (after) {
new_node->prev = old_node;
if (old_node) {
new_node->next = old_node->next;
if (old_node->next)
old_node->next->prev = new_node;
old_node->next = new_node;
}
if (quicklist->tail == old_node)
quicklist->tail = new_node;
} else {
new_node->next = old_node;
if (old_node) {
new_node->prev = old_node->prev;
if (old_node->prev)
old_node->prev->next = new_node;
old_node->prev = new_node;
}
if (quicklist->head == old_node)
quicklist->head = new_node;
}
/* If this insert creates the only element so far, initialize head/tail. */
if (quicklist->len == 0) {
quicklist->head = quicklist->tail = new_node;
}
if (old_node)
quicklistCompress(quicklist, old_node);
quicklist->len++;
}
/* Wrappers for node inserting around existing node. */
REDIS_STATIC void _quicklistInsertNodeBefore(quicklist *quicklist,
quicklistNode *old_node,
quicklistNode *new_node) {
__quicklistInsertNode(quicklist, old_node, new_node, 0);
}
REDIS_STATIC void _quicklistInsertNodeAfter(quicklist *quicklist,
quicklistNode *old_node,
quicklistNode *new_node) {
__quicklistInsertNode(quicklist, old_node, new_node, 1);
}
REDIS_STATIC int
_quicklistNodeSizeMeetsOptimizationRequirement(const size_t sz,
const int fill) {
if (fill >= 0)
return 0;
size_t offset = (-fill) - 1;
if (offset < (sizeof(optimization_level) / sizeof(*optimization_level))) {
if (sz <= optimization_level[offset]) {
return 1;
} else {
return 0;
}
} else {
return 0;
}
}
#define sizeMeetsSafetyLimit(sz) ((sz) <= SIZE_SAFETY_LIMIT)
REDIS_STATIC int _quicklistNodeAllowInsert(const quicklistNode *node,
const int fill, const size_t sz) {
if (unlikely(!node))
return 0;
int ziplist_overhead;
/* size of previous offset */
if (sz < 254)
ziplist_overhead = 1;
else
ziplist_overhead = 5;
/* size of forward offset */
if (sz < 64)
ziplist_overhead += 1;
else if (likely(sz < 16384))
ziplist_overhead += 2;
else
ziplist_overhead += 5;
/* new_sz overestimates if 'sz' encodes to an integer type */
unsigned int new_sz = node->sz + sz + ziplist_overhead;
if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(new_sz, fill)))
return 1;
else if (!sizeMeetsSafetyLimit(new_sz))
return 0;
else if ((int)node->count < fill)
return 1;
else
return 0;
}
REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a,
const quicklistNode *b,
const int fill) {
if (!a || !b)
return 0;
/* approximate merged ziplist size (- 11 to remove one ziplist
* header/trailer) */
unsigned int merge_sz = a->sz + b->sz - 11;
if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(merge_sz, fill)))
return 1;
else if (!sizeMeetsSafetyLimit(merge_sz))
return 0;
else if ((int)(a->count + b->count) <= fill)
return 1;
else
return 0;
}
#define quicklistNodeUpdateSz(node) \
do { \
(node)->sz = ziplistBlobLen((node)->zl); \
} while (0)
/* Add new entry to head node of quicklist.
*
* Returns 0 if used existing head.
* Returns 1 if new head created. */
int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
quicklistNode *orig_head = quicklist->head;
if (likely(
_quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
quicklist->head->zl =
ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);
quicklistNodeUpdateSz(quicklist->head);
} else {
quicklistNode *node = quicklistCreateNode();
node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
quicklistNodeUpdateSz(node);
_quicklistInsertNodeBefore(quicklist, quicklist->head, node);
}
quicklist->count++;
quicklist->head->count++;
return (orig_head != quicklist->head);
}
/* Add new entry to tail node of quicklist.
*
* Returns 0 if used existing tail.
* Returns 1 if new tail created. */
int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {
quicklistNode *orig_tail = quicklist->tail;
if (likely(
_quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) {
quicklist->tail->zl =
ziplistPush(quicklist->tail->zl, value, sz, ZIPLIST_TAIL);
quicklistNodeUpdateSz(quicklist->tail);
} else {
quicklistNode *node = quicklistCreateNode();
node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_TAIL);
quicklistNodeUpdateSz(node);
_quicklistInsertNodeAfter(quicklist, quicklist->tail, node);
}
quicklist->count++;
quicklist->tail->count++;
return (orig_tail != quicklist->tail);
}
/* Create new node consisting of a pre-formed ziplist.
* Used for loading RDBs where entire ziplists have been stored
* to be retrieved later. */
void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl) {
quicklistNode *node = quicklistCreateNode();
node->zl = zl;
node->count = ziplistLen(node->zl);
node->sz = ziplistBlobLen(zl);
_quicklistInsertNodeAfter(quicklist, quicklist->tail, node);
quicklist->count += node->count;
}
/* Append all values of ziplist 'zl' individually into 'quicklist'.
*
* This allows us to restore old RDB ziplists into new quicklists
* with smaller ziplist sizes than the saved RDB ziplist.
*
* Returns 'quicklist' argument. Frees passed-in ziplist 'zl' */
quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist,
unsigned char *zl) {
unsigned char *value;
unsigned int sz;
long long longval;
char longstr[32] = {0};
unsigned char *p = ziplistIndex(zl, 0);
while (ziplistGet(p, &value, &sz, &longval)) {
if (!value) {
/* Write the longval as a string so we can re-add it */
sz = ll2string(longstr, sizeof(longstr), longval);
value = (unsigned char *)longstr;
}
quicklistPushTail(quicklist, value, sz);
p = ziplistNext(zl, p);
}
zfree(zl);
return quicklist;
}
/* Create new (potentially multi-node) quicklist from a single existing ziplist.
*
* Returns new quicklist. Frees passed-in ziplist 'zl'. */
quicklist *quicklistCreateFromZiplist(int fill, int compress,
unsigned char *zl) {
return quicklistAppendValuesFromZiplist(quicklistNew(fill, compress), zl);
}
#define quicklistDeleteIfEmpty(ql, n) \
do { \
if ((n)->count == 0) { \
__quicklistDelNode((ql), (n)); \
(n) = NULL; \
} \
} while (0)
REDIS_STATIC void __quicklistDelNode(quicklist *quicklist,
quicklistNode *node) {
if (node->next)
node->next->prev = node->prev;
if (node->prev)
node->prev->next = node->next;
if (node == quicklist->tail) {
quicklist->tail = node->prev;
}
if (node == quicklist->head) {
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;
zfree(node->zl);
zfree(node);
quicklist->len--;
}
/* Delete one entry from list given the node for the entry and a pointer
* 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.
* Also updates in/out param 'p' with the next offset in the ziplist. */
REDIS_STATIC int quicklistDelIndex(quicklist *quicklist, quicklistNode *node,
unsigned char **p) {
int gone = 0;
node->zl = ziplistDelete(node->zl, p);
node->count--;
if (node->count == 0) {
gone = 1;
__quicklistDelNode(quicklist, node);
} else {
quicklistNodeUpdateSz(node);
}
quicklist->count--;
/* If we deleted the node, the original node is no longer valid */
return gone ? 1 : 0;
}
/* Delete one element represented by 'entry'
*
* 'entry' stores enough metadata to delete the proper position in
* the correct ziplist in the correct quicklist node. */
void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry) {
quicklistNode *prev = entry->node->prev;
quicklistNode *next = entry->node->next;
int deleted_node = quicklistDelIndex((quicklist *)entry->quicklist,
entry->node, &entry->zi);
/* after delete, the zi is now invalid for any future usage. */
iter->zi = NULL;
/* If current node is deleted, we must update iterator node and offset. */
if (deleted_node) {
if (iter->direction == AL_START_HEAD) {
iter->current = next;
iter->offset = 0;
} else if (iter->direction == AL_START_TAIL) {
iter->current = prev;
iter->offset = -1;
}
}
/* else if (!deleted_node), no changes needed.
* we already reset iter->zi above, and the existing iter->offset
* doesn't move again because:
* - [1, 2, 3] => delete offset 1 => [1, 3]: next element still offset 1
* - [1, 2, 3] => delete offset 0 => [2, 3]: next element still offset 0
* if we deleted the last element at offet N and now
* length of this ziplist is N-1, the next call into
* quicklistNext() will jump to the next node. */
}
/* Replace quicklist entry at offset 'index' by 'data' with length 'sz'.
*
* Returns 1 if replace happened.
* Returns 0 if replace failed and no changes happened. */
int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data,
int sz) {
quicklistEntry entry;
if (likely(quicklistIndex(quicklist, index, &entry))) {
/* quicklistIndex provides an uncompressed node */
entry.node->zl = ziplistDelete(entry.node->zl, &entry.zi);
entry.node->zl = ziplistInsert(entry.node->zl, entry.zi, data, sz);
quicklistCompress(quicklist, entry.node);
return 1;
} else {
return 0;
}
}
/* Given two nodes, try to merge their ziplists.
*
* This helps us not have a quicklist with 3 element ziplists if
* our fill factor can handle much higher levels.
*
* Note: 'a' must be to the LEFT of 'b'.
*
* After calling this function, both 'a' and 'b' should be considered
* unusable. The return value from this function must be used
* instead of re-using any of the quicklistNode input arguments.
*
* Returns the input node picked to merge against or NULL if
* merging was not possible. */
REDIS_STATIC quicklistNode *_quicklistZiplistMerge(quicklist *quicklist,
quicklistNode *a,
quicklistNode *b) {
D("Requested merge (a,b) (%u, %u)", a->count, b->count);
quicklistDecompressNode(a);
quicklistDecompressNode(b);
if ((ziplistMerge(&a->zl, &b->zl))) {
/* We merged ziplists! Now remove the unused quicklistNode. */
quicklistNode *keep = NULL, *nokeep = NULL;
if (!a->zl) {
nokeep = a;
keep = b;
} else if (!b->zl) {
nokeep = b;
keep = a;
}
keep->count = ziplistLen(keep->zl);
quicklistNodeUpdateSz(keep);
nokeep->count = 0;
__quicklistDelNode(quicklist, nokeep);
quicklistCompress(quicklist, keep);
return keep;
} else {
/* else, the merge returned NULL and nothing changed. */
return NULL;
}
}
/* Attempt to merge ziplists within two nodes on either side of 'center'.
*
* We attempt to merge:
* - (center->prev->prev, center->prev)
* - (center->next, center->next->next)
* - (center->prev, center)
* - (center, center->next)
*/
REDIS_STATIC void _quicklistMergeNodes(quicklist *quicklist,
quicklistNode *center) {
int fill = quicklist->fill;
quicklistNode *prev, *prev_prev, *next, *next_next, *target;
prev = prev_prev = next = next_next = target = NULL;
if (center->prev) {
prev = center->prev;
if (center->prev->prev)
prev_prev = center->prev->prev;
}
if (center->next) {
next = center->next;
if (center->next->next)
next_next = center->next->next;
}
/* Try to merge prev_prev and prev */
if (_quicklistNodeAllowMerge(prev, prev_prev, fill)) {
_quicklistZiplistMerge(quicklist, prev_prev, prev);
prev_prev = prev = NULL; /* they could have moved, invalidate them. */
}
/* Try to merge next and next_next */
if (_quicklistNodeAllowMerge(next, next_next, fill)) {
_quicklistZiplistMerge(quicklist, next, next_next);
next = next_next = NULL; /* they could have moved, invalidate them. */
}
/* Try to merge center node and previous node */
if (_quicklistNodeAllowMerge(center, center->prev, fill)) {
target = _quicklistZiplistMerge(quicklist, center->prev, center);
center = NULL; /* center could have been deleted, invalidate it. */
} else {
/* else, we didn't merge here, but target needs to be valid below. */
target = center;
}
/* Use result of center merge (or original) to merge with next node. */
if (_quicklistNodeAllowMerge(target, target->next, fill)) {
_quicklistZiplistMerge(quicklist, target, target->next);
}
}
/* Split 'node' into two parts, parameterized by 'offset' and 'after'.
*
* The 'after' argument controls which quicklistNode gets returned.
* If 'after'==1, returned node has elements after 'offset'.
* input node keeps elements up to 'offset', including 'offset'.
* If 'after'==0, returned node has elements up to 'offset', including 'offset'.
* input node keeps elements after 'offset'.
*
* If 'after'==1, returned node will have elements _after_ 'offset'.
* The returned node will have elements [OFFSET+1, END].
* The input node keeps elements [0, OFFSET].
*
* If 'after'==0, returned node will keep elements up to and including 'offset'.
* The returned node will have elements [0, OFFSET].
* The input node keeps elements [OFFSET+1, END].
*
* The input node keeps all elements not taken by the returned node.
*
* Returns newly created node or NULL if split not possible. */
REDIS_STATIC quicklistNode *_quicklistSplitNode(quicklistNode *node, int offset,
int after) {
size_t zl_sz = node->sz;
quicklistNode *new_node = quicklistCreateNode();
new_node->zl = zmalloc(zl_sz);
/* Copy original ziplist so we can split it */
memcpy(new_node->zl, node->zl, zl_sz);
/* -1 here means "continue deleting until the list ends" */
int orig_start = after ? offset + 1 : 0;
int orig_extent = after ? -1 : offset;
int new_start = after ? 0 : offset;
int new_extent = after ? offset + 1 : -1;
D("After %d (%d); ranges: [%d, %d], [%d, %d]", after, offset, orig_start,
orig_extent, new_start, new_extent);
node->zl = ziplistDeleteRange(node->zl, orig_start, orig_extent);
node->count = ziplistLen(node->zl);
quicklistNodeUpdateSz(node);
new_node->zl = ziplistDeleteRange(new_node->zl, new_start, new_extent);
new_node->count = ziplistLen(new_node->zl);
quicklistNodeUpdateSz(new_node);
D("After split lengths: orig (%d), new (%d)", node->count, new_node->count);
return new_node;
}
/* Insert a new entry before or after existing entry 'entry'.
*
* If after==1, the new value is inserted after 'entry', otherwise
* the new value is inserted before 'entry'. */
REDIS_STATIC void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry,
void *value, const size_t sz, int after) {
int full = 0, at_tail = 0, at_head = 0, full_next = 0, full_prev = 0;
int fill = quicklist->fill;
quicklistNode *node = entry->node;
quicklistNode *new_node = NULL;
if (!node) {
/* we have no reference node, so let's create only node in the list */
D("No node given!");
new_node = quicklistCreateNode();
new_node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
__quicklistInsertNode(quicklist, NULL, new_node, after);
new_node->count++;
quicklist->count++;
return;
}
/* Populate accounting flags for easier boolean checks later */
if (!_quicklistNodeAllowInsert(node, fill, sz)) {
D("Current node is full with count %d with requested fill %lu",
node->count, fill);
full = 1;
}
if (after && (entry->offset == node->count)) {
D("At Tail of current ziplist");
at_tail = 1;
if (!_quicklistNodeAllowInsert(node->next, fill, sz)) {
D("Next node is full too.");
full_next = 1;
}
}
if (!after && (entry->offset == 0)) {
D("At Head");
at_head = 1;
if (!_quicklistNodeAllowInsert(node->prev, fill, sz)) {
D("Prev node is full too.");
full_prev = 1;
}
}
/* Now determine where and how to insert the new element */
if (!full && after) {
D("Not full, inserting after current position.");
quicklistDecompressNodeForUse(node);
unsigned char *next = ziplistNext(node->zl, entry->zi);
if (next == NULL) {
node->zl = ziplistPush(node->zl, value, sz, ZIPLIST_TAIL);
} else {
node->zl = ziplistInsert(node->zl, next, value, sz);
}
node->count++;
quicklistNodeUpdateSz(node);
quicklistRecompressOnly(quicklist, node);
} else if (!full && !after) {
D("Not full, inserting before current position.");
quicklistDecompressNodeForUse(node);
node->zl = ziplistInsert(node->zl, entry->zi, value, sz);
node->count++;
quicklistNodeUpdateSz(node);
quicklistRecompressOnly(quicklist, node);
} else if (full && at_tail && node->next && !full_next && after) {
/* If we are: at tail, next has free space, and inserting after:
* - insert entry at head of next node. */
D("Full and tail, but next isn't full; inserting next node head");
new_node = node->next;
quicklistDecompressNodeForUse(new_node);
new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_HEAD);
new_node->count++;
quicklistNodeUpdateSz(new_node);
quicklistRecompressOnly(quicklist, new_node);
} else if (full && at_head && node->prev && !full_prev && !after) {
/* If we are: at head, previous has free space, and inserting before:
* - insert entry at tail of previous node. */
D("Full and head, but prev isn't full, inserting prev node tail");
new_node = node->prev;
quicklistDecompressNodeForUse(new_node);
new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_TAIL);
new_node->count++;
quicklistNodeUpdateSz(new_node);
quicklistRecompressOnly(quicklist, new_node);
} else if (full && ((at_tail && node->next && full_next && after) ||
(at_head && node->prev && full_prev && !after))) {
/* If we are: full, and our prev/next is full, then:
* - create new node and attach to quicklist */
D("\tprovisioning new node...");
new_node = quicklistCreateNode();
new_node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
new_node->count++;
quicklistNodeUpdateSz(new_node);
__quicklistInsertNode(quicklist, node, new_node, after);
} else if (full) {
/* else, node is full we need to split it. */
/* covers both after and !after cases */
D("\tsplitting node...");
quicklistDecompressNodeForUse(node);
new_node = _quicklistSplitNode(node, entry->offset, after);
new_node->zl = ziplistPush(new_node->zl, value, sz,
after ? ZIPLIST_HEAD : ZIPLIST_TAIL);
new_node->count++;
quicklistNodeUpdateSz(new_node);
__quicklistInsertNode(quicklist, node, new_node, after);
_quicklistMergeNodes(quicklist, node);
}
quicklist->count++;
}
void quicklistInsertBefore(quicklist *quicklist, quicklistEntry *entry,
void *value, const size_t sz) {
_quicklistInsert(quicklist, entry, value, sz, 0);
}
void quicklistInsertAfter(quicklist *quicklist, quicklistEntry *entry,
void *value, const size_t sz) {
_quicklistInsert(quicklist, entry, value, sz, 1);
}
/* Delete a range of elements from the quicklist.
*
* elements may span across multiple quicklistNodes, so we
* have to be careful about tracking where we start and end.
*
* Returns 1 if entries were deleted, 0 if nothing was deleted. */
int quicklistDelRange(quicklist *quicklist, const long start,
const long count) {
if (count <= 0)
return 0;
unsigned long extent = count; /* range is inclusive of start position */
if (start >= 0 && extent > (quicklist->count - start)) {
/* if requesting delete more elements than exist, limit to list size. */
extent = quicklist->count - start;
} else if (start < 0 && extent > (unsigned long)(-start)) {
/* else, if at negative offset, limit max size to rest of list. */
extent = -start; /* c.f. LREM -29 29; just delete until end. */
}
quicklistEntry entry;
if (!quicklistIndex(quicklist, start, &entry))
return 0;
D("Quicklist delete request for start %ld, count %ld, extent: %ld", start,
count, extent);
quicklistNode *node = entry.node;
/* iterate over next nodes until everything is deleted. */
while (extent) {
quicklistNode *next = node->next;
unsigned long del;
int delete_entire_node = 0;
if (entry.offset == 0 && extent >= node->count) {
/* If we are deleting more than the count of this node, we
* can just delete the entire node without ziplist math. */
delete_entire_node = 1;
del = node->count;
} else if (entry.offset >= 0 && extent >= node->count) {
/* If deleting more nodes after this one, calculate delete based
* on size of current node. */
del = node->count - entry.offset;
} else if (entry.offset < 0) {
/* If offset is negative, we are in the first run of this loop
* and we are deleting the entire range
* from this start offset to end of list. Since the Negative
* offset is the number of elements until the tail of the list,
* just use it directly as the deletion count. */
del = -entry.offset;
/* If the positive offset is greater than the remaining extent,
* we only delete the remaining extent, not the entire offset.
*/
if (del > extent)
del = extent;
} else {
/* else, we are deleting less than the extent of this node, so
* use extent directly. */
del = extent;
}
D("[%ld]: asking to del: %ld because offset: %d; (ENTIRE NODE: %d), "
"node count: %u",
extent, del, entry.offset, delete_entire_node, node->count);
if (delete_entire_node) {
__quicklistDelNode(quicklist, node);
} else {
quicklistDecompressNodeForUse(node);
node->zl = ziplistDeleteRange(node->zl, entry.offset, del);
quicklistNodeUpdateSz(node);
node->count -= del;
quicklist->count -= del;
quicklistDeleteIfEmpty(quicklist, node);
if (node)
quicklistRecompressOnly(quicklist, node);
}
extent -= del;
node = next;
entry.offset = 0;
}
return 1;
}
/* Passthrough to ziplistCompare() */
int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len) {
return ziplistCompare(p1, p2, p2_len);
}
/* Returns a quicklist iterator 'iter'. After the initialization every
* call to quicklistNext() will return the next element of the quicklist. */
quicklistIter *quicklistGetIterator(const quicklist *quicklist, int direction) {
quicklistIter *iter;
iter = zmalloc(sizeof(*iter));
if (direction == AL_START_HEAD) {
iter->current = quicklist->head;
iter->offset = 0;
} else if (direction == AL_START_TAIL) {
iter->current = quicklist->tail;
iter->offset = -1;
}
iter->direction = direction;
iter->quicklist = quicklist;
iter->zi = NULL;
return iter;
}
/* Initialize an iterator at a specific offset 'idx' and make the iterator
* return nodes in 'direction' direction. */
quicklistIter *quicklistGetIteratorAtIdx(const quicklist *quicklist,
const int direction,
const long long idx) {
quicklistEntry entry;
if (quicklistIndex(quicklist, idx, &entry)) {
quicklistIter *base = quicklistGetIterator(quicklist, direction);
base->zi = NULL;
base->current = entry.node;
base->offset = entry.offset;
return base;
} else {
return NULL;
}
}
/* Release iterator.
* 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.
*
* Note: You must NOT insert into the list while iterating over it.
* You *may* delete from the list while iterating using the
* quicklistDelEntry() function.
* If you insert into the quicklist while iterating, you should
* re-create the iterator after your addition.
*
* iter = quicklistGetIterator(quicklist,<direction>);
* quicklistEntry entry;
* while (quicklistNext(iter, &entry)) {
* if (entry.value)
* [[ use entry.value with entry.sz ]]
* else
* [[ use entry.longval ]]
* }
*
* Populates 'entry' with values for this iteration.
* Returns 0 when iteration is complete or if iteration not possible.
* If return value is 0, the contents of 'entry' are not valid.
*/
int quicklistNext(quicklistIter *iter, quicklistEntry *entry) {
initEntry(entry);
if (!iter) {
D("Returning because no iter!");
return 0;
}
entry->quicklist = iter->quicklist;
entry->node = iter->current;
if (!iter->current) {
D("Returning because current node is NULL")
return 0;
}
unsigned char *(*nextFn)(unsigned char *, unsigned char *) = NULL;
int offset_update = 0;
if (!iter->zi) {
/* If !zi, use current index. */
quicklistDecompressNodeForUse(iter->current);
iter->zi = ziplistIndex(iter->current->zl, iter->offset);
} else {
/* else, use existing iterator offset and get prev/next as necessary. */
if (iter->direction == AL_START_HEAD) {
nextFn = ziplistNext;
offset_update = 1;
} else if (iter->direction == AL_START_TAIL) {
nextFn = ziplistPrev;
offset_update = -1;
}
iter->zi = nextFn(iter->current->zl, iter->zi);
iter->offset += offset_update;
}
entry->zi = iter->zi;
entry->offset = iter->offset;
if (iter->zi) {
/* Populate value from existing ziplist position */
ziplistGet(entry->zi, &entry->value, &entry->sz, &entry->longval);
return 1;
} else {
/* We ran out of ziplist entries.
* Pick next node, update offset, then re-run retrieval. */
quicklistCompress(iter->quicklist, iter->current);
if (iter->direction == AL_START_HEAD) {
/* Forward traversal */
D("Jumping to start of next node");
iter->current = iter->current->next;
iter->offset = 0;
} else if (iter->direction == AL_START_TAIL) {
/* Reverse traversal */
D("Jumping to end of previous node");
iter->current = iter->current->prev;
iter->offset = -1;
}
iter->zi = NULL;
return quicklistNext(iter, entry);
}
}
/* Duplicate the quicklist.
* On success a copy of the original quicklist is returned.
*
* The original quicklist both on success or error is never modified.
*
* Returns newly allocated quicklist. */
quicklist *quicklistDup(quicklist *orig) {
quicklist *copy;
copy = quicklistNew(orig->fill, orig->compress);
for (quicklistNode *current = orig->head; current;
current = current->next) {
quicklistNode *node = quicklistCreateNode();
if (node->encoding == QUICKLIST_NODE_ENCODING_LZF) {
quicklistLZF *lzf = (quicklistLZF *)node->zl;
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;
copy->count += node->count;
node->sz = current->sz;
node->encoding = current->encoding;
_quicklistInsertNodeAfter(copy, copy->tail, node);
}
/* copy->count must equal orig->count here */
return copy;
}
/* Populate 'entry' with the element at the specified zero-based index
* where 0 is the head, 1 is the element next to head
* and so on. Negative integers are used in order to count
* from the tail, -1 is the last element, -2 the penultimate
* and so on. If the index is out of range 0 is returned.
*
* Returns 1 if element found
* Returns 0 if element not found */
int quicklistIndex(const quicklist *quicklist, const long long idx,
quicklistEntry *entry) {
quicklistNode *n;
unsigned long long accum = 0;
unsigned long long index;
int forward = idx < 0 ? 0 : 1; /* < 0 -> reverse, 0+ -> forward */
initEntry(entry);
entry->quicklist = quicklist;
if (!forward) {
index = (-idx) - 1;
n = quicklist->tail;
} else {
index = idx;
n = quicklist->head;
}
if (index >= quicklist->count)
return 0;
while (likely(n)) {
if ((accum + n->count) > index) {
break;
} else {
D("Skipping over (%p) %u at accum %lld", (void *)n, n->count,
accum);
accum += n->count;
n = forward ? n->next : n->prev;
}
}
if (!n)
return 0;
D("Found node: %p at accum %llu, idx %llu, sub+ %llu, sub- %llu", (void *)n,
accum, index, index - accum, (-index) - 1 + accum);
entry->node = n;
if (forward) {
/* forward = normal head-to-tail offset. */
entry->offset = index - accum;
} else {
/* reverse = need negative offset for tail-to-head, so undo
* the result of the original if (index < 0) above. */
entry->offset = (-index) - 1 + accum;
}
quicklistDecompressNodeForUse(entry->node);
entry->zi = ziplistIndex(entry->node->zl, entry->offset);
ziplistGet(entry->zi, &entry->value, &entry->sz, &entry->longval);
/* The caller will use our result, so we don't re-compress here.
* The caller can recompress or delete the node as needed. */
return 1;
}
/* Rotate quicklist by moving the tail element to the head. */
void quicklistRotate(quicklist *quicklist) {
if (quicklist->count <= 1)
return;
/* First, get the tail entry */
unsigned char *p = ziplistIndex(quicklist->tail->zl, -1);
unsigned char *value;
long long longval;
unsigned int sz;
char longstr[32] = {0};
ziplistGet(p, &value, &sz, &longval);
/* If value found is NULL, then ziplistGet populated longval instead */
if (!value) {
/* Write the longval as a string so we can re-add it */
sz = ll2string(longstr, sizeof(longstr), longval);
value = (unsigned char *)longstr;
}
/* Add tail entry to head (must happen before tail is deleted). */
quicklistPushHead(quicklist, value, sz);
/* If quicklist has only one node, the head ziplist is also the
* tail ziplist and PushHead() could have reallocated our single ziplist,
* which would make our pre-existing 'p' unusable. */
if (quicklist->len == 1) {
p = ziplistIndex(quicklist->tail->zl, -1);
}
/* Remove tail entry. */
quicklistDelIndex(quicklist, quicklist->tail, &p);
}
/* pop from quicklist and return result in 'data' ptr. Value of 'data'
* is the return value of 'saver' function pointer if the data is NOT a number.
*
* If the quicklist element is a long long, then the return value is returned in
* 'sval'.
*
* Return value of 0 means no elements available.
* Return value of 1 means check 'data' and 'sval' for values.
* If 'data' is set, use 'data' and 'sz'. Otherwise, use 'sval'. */
int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
unsigned int *sz, long long *sval,
void *(*saver)(unsigned char *data, unsigned int sz)) {
unsigned char *p;
unsigned char *vstr;
unsigned int vlen;
long long vlong;
int pos = (where == QUICKLIST_HEAD) ? 0 : -1;
if (quicklist->count == 0)
return 0;
if (data)
*data = NULL;
if (sz)
*sz = 0;
if (sval)
*sval = -123456789;
quicklistNode *node;
if (where == QUICKLIST_HEAD && quicklist->head) {
node = quicklist->head;
} else if (where == QUICKLIST_TAIL && quicklist->tail) {
node = quicklist->tail;
} else {
return 0;
}
p = ziplistIndex(node->zl, pos);
if (ziplistGet(p, &vstr, &vlen, &vlong)) {
if (vstr) {
if (data)
*data = saver(vstr, vlen);
if (sz)
*sz = vlen;
} else {
if (data)
*data = NULL;
if (sval)
*sval = vlong;
}
quicklistDelIndex(quicklist, node, &p);
return 1;
}
return 0;
}
/* Return a malloc'd copy of data passed in */
REDIS_STATIC void *_quicklistSaver(unsigned char *data, unsigned int sz) {
unsigned char *vstr;
if (data) {
vstr = zmalloc(sz);
memcpy(data, vstr, sz);
return vstr;
}
return NULL;
}
/* Default pop function
*
* Returns malloc'd value from quicklist */
int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
unsigned int *sz, long long *slong) {
unsigned char *vstr;
unsigned int vlen;
long long vlong;
if (quicklist->count == 0)
return 0;
int ret = quicklistPopCustom(quicklist, where, &vstr, &vlen, &vlong,
_quicklistSaver);
if (data)
*data = vstr;
if (slong)
*slong = vlong;
if (sz)
*sz = vlen;
return ret;
}
/* Wrapper to allow argument-based switching between HEAD/TAIL pop */
void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
int where) {
if (where == QUICKLIST_HEAD) {
quicklistPushHead(quicklist, value, sz);
} else if (where == QUICKLIST_TAIL) {
quicklistPushTail(quicklist, value, sz);
}
}
/* The rest of this file is test cases and test helpers. */
#ifdef REDIS_TEST
#include <stdint.h>
#include <sys/time.h>
#define assert(_e) \
do { \
if (!(_e)) { \
printf("\n\n=== ASSERTION FAILED ===\n"); \
printf("==> %s:%d '%s' is not true\n", __FILE__, __LINE__, #_e); \
err++; \
} \
} while (0)
#define yell(str, ...) printf("ERROR! " str "\n\n", __VA_ARGS__)
#define OK printf("\tOK\n")
#define ERROR \
do { \
printf("\tERROR!\n"); \
err++; \
} while (0)
#define ERR(x, ...) \
do { \
printf("%s:%s:%d:\t", __FILE__, __FUNCTION__, __LINE__); \
printf("ERROR! " x "\n", __VA_ARGS__); \
err++; \
} while (0)
#define TEST(name) printf("test — %s\n", name);
#define TEST_DESC(name, ...) printf("test — " name "\n", __VA_ARGS__);
#define QL_TEST_VERBOSE 0
#define UNUSED(x) (void)(x)
static void ql_info(quicklist *ql) {
#if QL_TEST_VERBOSE
printf("Container length: %lu\n", ql->len);
printf("Container size: %lu\n", ql->count);
if (ql->head)
printf("\t(zsize head: %d)\n", ziplistLen(ql->head->zl));
if (ql->tail)
printf("\t(zsize tail: %d)\n", ziplistLen(ql->tail->zl));
printf("\n");
#else
UNUSED(ql);
#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.
* Print the list if 'print' == 1.
*
* Returns physical count of elements found by iterating over the list. */
static int _itrprintr(quicklist *ql, int print, int forward) {
quicklistIter *iter =
quicklistGetIterator(ql, forward ? AL_START_HEAD : AL_START_TAIL);
quicklistEntry entry;
int i = 0;
int p = 0;
quicklistNode *prev = NULL;
while (quicklistNext(iter, &entry)) {
if (entry.node != prev) {
/* Count the number of list nodes too */
p++;
prev = entry.node;
}
if (print) {
printf("[%3d (%2d)]: [%.*s] (%lld)\n", i, p, entry.sz,
(char *)entry.value, entry.longval);
}
i++;
}
quicklistReleaseIterator(iter);
return i;
}
static int itrprintr(quicklist *ql, int print) {
return _itrprintr(ql, print, 1);
}
static int itrprintr_rev(quicklist *ql, int print) {
return _itrprintr(ql, print, 0);
}
#define ql_verify(a, b, c, d, e) \
do { \
err += _ql_verify((a), (b), (c), (d), (e)); \
} while (0)
/* Verify list metadata matches physical list contents. */
static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count,
uint32_t head_count, uint32_t tail_count) {
int errors = 0;
ql_info(ql);
if (len != ql->len) {
yell("quicklist length wrong: expected %d, got %u", len, ql->len);
errors++;
}
if (count != ql->count) {
yell("quicklist count wrong: expected %d, got %lu", count, ql->count);
errors++;
}
int loopr = itrprintr(ql, 0);
if (loopr != (int)ql->count) {
yell("quicklist cached count not match actual count: expected %lu, got "
"%d",
ql->count, loopr);
errors++;
}
int rloopr = itrprintr_rev(ql, 0);
if (loopr != rloopr) {
yell("quicklist has different forward count than reverse count! "
"Forward count is %d, reverse count is %d.",
loopr, rloopr);
errors++;
}
if (ql->len == 0 && !errors) {
OK;
return errors;
}
if (ql->head && head_count != ql->head->count &&
head_count != ziplistLen(ql->head->zl)) {
yell("quicklist head count wrong: expected %d, "
"got cached %d vs. actual %d",
head_count, ql->head->count, ziplistLen(ql->head->zl));
errors++;
}
if (ql->tail && tail_count != ql->tail->count &&
tail_count != ziplistLen(ql->tail->zl)) {
yell("quicklist tail count wrong: expected %d, "
"got cached %u vs. actual %d",
tail_count, ql->tail->count, ziplistLen(ql->tail->zl));
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 (!errors)
OK;
return errors;
}
/* Generate new string concatenating integer i against string 'prefix' */
static char *genstr(char *prefix, int i) {
static char result[64] = {0};
snprintf(result, sizeof(result), "%s%d", prefix, i);
return result;
}
/* main test, but callable from other files */
int quicklistTest(int argc, char *argv[]) {
UNUSED(argc);
UNUSED(argv);
unsigned int err = 0;
int optimize_start =
-(int)(sizeof(optimization_level) / sizeof(*optimization_level));
printf("Starting optimization offset at: %d\n", optimize_start);
int options[] = {0, 1, 2, 3, 4, 5, 6, 10};
size_t option_count = sizeof(options) / sizeof(*options);
long long runtime[option_count];
for (int _i = 0; _i < (int)option_count; _i++) {
printf("Testing Option %d\n", options[_i]);
long long start = mstime();
TEST("create list") {
quicklist *ql = quicklistNew(-2, options[_i]);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("add to tail of empty list") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistPushTail(ql, "hello", 6);
/* 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 = quicklistNew(-2, options[_i]);
quicklistPushHead(ql, "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 at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
for (int i = 0; i < 5; i++)
quicklistPushTail(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++) {
TEST_DESC("add to head 5x at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
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 < 512; f++) {
TEST_DESC("add to tail 500x at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i), 64);
if (ql->count != 500)
ERROR;
if (f == 32)
ql_verify(ql, 16, 500, 32, 20);
quicklistRelease(ql);
}
}
for (int f = optimize_start; f < 512; f++) {
TEST_DESC("add to head 500x at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, genstr("hello", i), 32);
if (ql->count != 500)
ERROR;
if (f == 32)
ql_verify(ql, 16, 500, 20, 32);
quicklistRelease(ql);
}
}
TEST("rotate empty") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistRotate(ql);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
for (int f = optimize_start; f < 32; f++) {
TEST("rotate one val once") {
quicklist *ql = quicklistNew(f, options[_i]);
quicklistPushHead(ql, "hello", 6);
quicklistRotate(ql);
/* Ignore compression verify because ziplist is
* too small to compress. */
ql_verify(ql, 1, 1, 1, 1);
quicklistRelease(ql);
}
}
for (int f = optimize_start; f < 3; f++) {
TEST_DESC("rotate 500 val 5000 times at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
quicklistPushHead(ql, "900", 3);
quicklistPushHead(ql, "7000", 4);
quicklistPushHead(ql, "-1200", 5);
quicklistPushHead(ql, "42", 2);
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, genstr("hello", i), 64);
ql_info(ql);
for (int i = 0; i < 5000; i++) {
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 empty") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistPop(ql, QUICKLIST_HEAD, NULL, NULL, NULL);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("pop 1 string from 1") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistPushHead(ql, genstr("hello", 331), 32);
unsigned char *data;
unsigned int sz;
long long lv;
ql_info(ql);
quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
assert(data != NULL);
assert(sz == 32);
zfree(data);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("pop head 1 number from 1") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistPushHead(ql, "55513", 5);
unsigned char *data;
unsigned int sz;
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") {
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(data != NULL);
assert(sz == 32);
zfree(data);
}
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("pop head 5000 from 500") {
quicklist *ql = quicklistNew(-2, options[_i]);
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, genstr("hello", i), 32);
for (int i = 0; i < 5000; i++) {
unsigned char *data;
unsigned int sz;
long long lv;
int ret = quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
if (i < 500) {
assert(ret == 1);
assert(data != NULL);
assert(sz == 32);
zfree(data);
} else {
assert(ret == 0);
}
}
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("iterate forward over 500 list") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistSetFill(ql, 32);
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, 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 = quicklistNew(-2, options[_i]);
quicklistSetFill(ql, 32);
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, 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 = quicklistNew(-2, options[_i]);
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
quicklistInsertBefore(ql, &entry, "abc", 4);
ql_verify(ql, 1, 1, 1, 1);
quicklistRelease(ql);
}
TEST("insert after with 0 elements") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
quicklistInsertAfter(ql, &entry, "abc", 4);
ql_verify(ql, 1, 1, 1, 1);
quicklistRelease(ql);
}
TEST("insert after 1 element") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistPushHead(ql, "hello", 6);
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
quicklistInsertAfter(ql, &entry, "abc", 4);
ql_verify(ql, 1, 2, 2, 2);
quicklistRelease(ql);
}
TEST("insert before 1 element") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistPushHead(ql, "hello", 6);
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
quicklistInsertAfter(ql, &entry, "abc", 4);
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 at "
"compress %d\n",
f, options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
quicklistPushTail(ql, "abc", 3);
quicklistSetFill(ql, 1);
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);
}
}
for (int f = optimize_start; f < 1024; f++) {
TEST_DESC(
"insert [before] 250 new in middle of 500 elements at fill"
" %d at compress %d",
f, options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
for (int i = 0; i < 500; i++)
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);
}
}
for (int f = optimize_start; f < 1024; f++) {
TEST_DESC("insert [after] 250 new in middle of 500 elements at "
"fill %d at compress %d",
f, options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, genstr("hello", i), 32);
for (int i = 0; i < 250; i++) {
quicklistEntry entry;
quicklistIndex(ql, 250, &entry);
quicklistInsertAfter(ql, &entry, genstr("abc", i), 32);
}
if (ql->count != 750)
ERR("List size not 750, but rather %ld", ql->count);
if (f == 32)
ql_verify(ql, 26, 750, 20, 32);
quicklistRelease(ql);
}
}
TEST("duplicate empty list") {
quicklist *ql = quicklistNew(-2, options[_i]);
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 = quicklistNew(-2, options[_i]);
quicklistPushHead(ql, 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 = quicklistNew(-2, options[_i]);
quicklistSetFill(ql, 32);
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, genstr("hello", i), 32);
ql_verify(ql, 16, 500, 20, 32);
quicklist *copy = quicklistDup(ql);
ql_verify(copy, 16, 500, 20, 32);
quicklistRelease(ql);
quicklistRelease(copy);
}
for (int f = optimize_start; f < 512; f++) {
TEST_DESC("index 1,200 from 500 list at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i + 1), 32);
quicklistEntry entry;
quicklistIndex(ql, 1, &entry);
if (!strcmp((char *)entry.value, "hello2"))
OK;
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);
}
TEST_DESC("index -1,-2 from 500 list at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i + 1), 32);
quicklistEntry entry;
quicklistIndex(ql, -1, &entry);
if (!strcmp((char *)entry.value, "hello500"))
OK;
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);
}
TEST_DESC("index -100 from 500 list at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i + 1), 32);
quicklistEntry entry;
quicklistIndex(ql, -100, &entry);
if (!strcmp((char *)entry.value, "hello401"))
OK;
else
ERR("Value: %s", entry.value);
quicklistRelease(ql);
}
TEST_DESC("index too big +1 from 50 list at fill %d at compress %d",
f, options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
for (int i = 0; i < 50; i++)
quicklistPushTail(ql, genstr("hello", i + 1), 32);
quicklistEntry entry;
if (quicklistIndex(ql, 50, &entry))
ERR("Index found at 50 with 50 list: %.*s", entry.sz,
entry.value);
else
OK;
quicklistRelease(ql);
}
}
TEST("delete range empty list") {
quicklist *ql = quicklistNew(-2, options[_i]);
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 = quicklistNew(-2, options[_i]);
for (int i = 0; i < 32; i++)
quicklistPushHead(ql, 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") {
quicklist *ql = quicklistNew(-2, options[_i]);
for (int i = 0; i < 32; i++)
quicklistPushHead(ql, genstr("hello", i), 32);
ql_verify(ql, 1, 32, 32, 32);
quicklistDelRange(ql, 0, 128);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("delete middle 100 of 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);
ql_verify(ql, 16, 500, 32, 20);
quicklistDelRange(ql, 200, 100);
ql_verify(ql, 14, 400, 32, 20);
quicklistRelease(ql);
}
TEST("delete negative 1 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);
ql_verify(ql, 16, 500, 32, 20);
quicklistDelRange(ql, -1, 1);
ql_verify(ql, 16, 499, 32, 19);
quicklistRelease(ql);
}
TEST("delete negative 1 from 500 list with overflow counts") {
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, -1, 128);
ql_verify(ql, 16, 499, 32, 19);
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;
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") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistSetFill(ql, 32);
char num[32];
long long nums[5000];
for (int i = 0; i < 5000; i++) {
nums[i] = -5157318210846258176 + i;
int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, num, sz);
}
quicklistPushTail(ql, "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 *ql = quicklistNew(-2, options[_i]);
quicklistPushTail(ql, "99", 2);
quicklistPushTail(ql, "98", 2);
quicklistPushTail(ql, "xxxxxxxxxxxxxxxxxxxx", 20);
quicklistPushTail(ql, "96", 2);
quicklistPushTail(ql, "95", 2);
quicklistReplaceAtIndex(ql, 1, "foo", 3);
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);
}
}
for (int f = optimize_start; f < 16; f++) {
TEST_DESC("iterate reverse + delete at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
quicklistPushTail(ql, "abc", 3);
quicklistPushTail(ql, "def", 3);
quicklistPushTail(ql, "hij", 3);
quicklistPushTail(ql, "jkl", 3);
quicklistPushTail(ql, "oop", 3);
quicklistEntry entry;
quicklistIter *iter = quicklistGetIterator(ql, AL_START_TAIL);
int i = 0;
while (quicklistNext(iter, &entry)) {
if (quicklistCompare(entry.zi, (unsigned char *)"hij", 3)) {
quicklistDelEntry(iter, &entry);
}
i++;
}
quicklistReleaseIterator(iter);
if (i != 5)
ERR("Didn't iterate 5 times, iterated %d times.", i);
/* Check results after deletion of "hij" */
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);
}
}
for (int f = optimize_start; f < 800; f++) {
TEST_DESC("iterator at index test 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 < 760; i++) {
nums[i] = -5157318210846258176 + i;
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);
}
}
for (int f = optimize_start; f < 40; f++) {
TEST_DESC("ltrim test A 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 < 32; i++) {
nums[i] = -5157318210846258176 + i;
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);
}
}
for (int f = optimize_start; f < 40; f++) {
TEST_DESC("ltrim test B at fill %d at compress %d", f,
options[_i]) {
/* Force-disable compression because our 33 sequential
* integers don't compress and the check always fails. */
quicklist *ql = quicklistNew(f, QUICKLIST_NOCOMPRESS);
char num[32];
long long nums[5000];
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 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);
}
}
}
}
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)
printf("ALL TESTS PASSED!\n");
else
ERR("Sorry, not all tests passed! In fact, %d tests failed.", err);
return err;
}
#endif
/* quicklist.h - A generic doubly linked quicklist implementation
*
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this quicklist of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this quicklist of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __QUICKLIST_H__
#define __QUICKLIST_H__
/* 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 {
struct quicklistNode *prev;
struct quicklistNode *next;
unsigned char *zl;
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;
/* 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 {
quicklistNode *head;
quicklistNode *tail;
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;
typedef struct quicklistIter {
const quicklist *quicklist;
quicklistNode *current;
unsigned char *zi;
long offset; /* offset in current ziplist */
int direction;
} quicklistIter;
typedef struct quicklistEntry {
const quicklist *quicklist;
quicklistNode *node;
unsigned char *zi;
unsigned char *value;
unsigned int sz;
long long longval;
int offset;
} quicklistEntry;
#define QUICKLIST_HEAD 0
#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 */
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);
int quicklistPushHead(quicklist *quicklist, void *value, const size_t sz);
int quicklistPushTail(quicklist *quicklist, void *value, const size_t sz);
void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
int where);
void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl);
quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist,
unsigned char *zl);
quicklist *quicklistCreateFromZiplist(int fill, int compress,
unsigned char *zl);
void quicklistInsertAfter(quicklist *quicklist, 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);
int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data,
int sz);
int quicklistDelRange(quicklist *quicklist, const long start, const long stop);
quicklistIter *quicklistGetIterator(const quicklist *quicklist, int direction);
quicklistIter *quicklistGetIteratorAtIdx(const quicklist *quicklist,
int direction, const long long idx);
int quicklistNext(quicklistIter *iter, quicklistEntry *node);
void quicklistReleaseIterator(quicklistIter *iter);
quicklist *quicklistDup(quicklist *orig);
int quicklistIndex(const quicklist *quicklist, const long long index,
quicklistEntry *entry);
void quicklistRewind(quicklist *quicklist, quicklistIter *li);
void quicklistRewindTail(quicklist *quicklist, quicklistIter *li);
void quicklistRotate(quicklist *quicklist);
int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
unsigned int *sz, long long *sval,
void *(*saver)(unsigned char *data, unsigned int sz));
int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
unsigned int *sz, long long *slong);
unsigned int quicklistCount(quicklist *ql);
int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len);
size_t quicklistGetLzf(const quicklistNode *node, void **data);
#ifdef REDIS_TEST
int quicklistTest(int argc, char *argv[]);
#endif
/* Directions for iterators */
#define AL_START_HEAD 0
#define AL_START_TAIL 1
#endif /* __QUICKLIST_H__ */
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