From 12fea9289063753ddd478f484636c343eeef49ba Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 8 Jun 2009 23:51:35 +0200 Subject: [PATCH] SPOP implemented. Hash table resizing for Sets and Expires too. Changed the resize policy to play better with RANDOMKEY and SPOP. --- dict.h | 2 +- doc/Credits.html | 2 +- doc/SinterCommand.html | 2 +- redis-cli.c | 1 + redis.c | 58 +++++++++++++++++++++++++++++++++++------- test-redis.tcl | 8 ++++++ 6 files changed, 61 insertions(+), 12 deletions(-) diff --git a/dict.h b/dict.h index 90d46f488..bd935d5f4 100644 --- a/dict.h +++ b/dict.h @@ -73,7 +73,7 @@ typedef struct dictIterator { } dictIterator; /* This is the initial size of every hash table */ -#define DICT_HT_INITIAL_SIZE 16 +#define DICT_HT_INITIAL_SIZE 4 /* ------------------------------- Macros ------------------------------------*/ #define dictFreeEntryVal(ht, entry) \ diff --git a/doc/Credits.html b/doc/Credits.html index e4e35fa43..fefc44400 100644 --- a/doc/Credits.html +++ b/doc/Credits.html @@ -26,7 +26,7 @@
-

Credits

+

Credits

p.s. sorry to take this file in sync is hard in this early days. Please drop me an email if I forgot to add your name here!
diff --git a/doc/SinterCommand.html b/doc/SinterCommand.html index 0d1267dca..7a8259b49 100644 --- a/doc/SinterCommand.html +++ b/doc/SinterCommand.html @@ -27,7 +27,7 @@

SINTER _key1_ _key2_ ... _keyN_

-Time complexity O(NM) worst case where N is the cardinality of the smallest set and M the number of sets_

Return the members of a set resulting from the intersection of all thesets hold at the specified keys. Like in LRANGE the result is sent tothe client as a multi-bulk reply (see the protocol specification formore information). If just a single key is specified, then this commandproduces the same result as SELEMENTS. Actually SELEMENTS is just syntaxsugar for SINTERSECT.
+Time complexity O(NM) worst case where N is the cardinality of the smallest set and M the number of sets_

Return the members of a set resulting from the intersection of all thesets hold at the specified keys. Like in LRANGE the result is sent tothe client as a multi-bulk reply (see the protocol specification formore information). If just a single key is specified, then this commandproduces the same result as SMEMBERS. Actually SMEMBERS is just syntaxsugar for SINTERSECT.
Non existing keys are considered like empty sets, so if one of the keys ismissing an empty set is returned (since the intersection with an emptyset always is an empty set).

Return value

Multi bulk reply, specifically the list of common elements.

See also

* SADD* SREM* SISMEMBER* SCARD* SMEMBERS* SINTERSTORE* SUNION* SUNIONSTORE* SMOVE
diff --git a/redis-cli.c b/redis-cli.c index 343ee0a14..9acf92dc2 100644 --- a/redis-cli.c +++ b/redis-cli.c @@ -79,6 +79,7 @@ static struct redisCommand cmdTable[] = { {"smove",4,REDIS_CMD_BULK}, {"sismember",3,REDIS_CMD_BULK}, {"scard",2,REDIS_CMD_INLINE}, + {"spop",2,REDIS_CMD_INLINE}, {"sinter",-2,REDIS_CMD_INLINE}, {"sinterstore",-3,REDIS_CMD_INLINE}, {"sunion",-2,REDIS_CMD_INLINE}, diff --git a/redis.c b/redis.c index 2caf06c34..fa653d21e 100644 --- a/redis.c +++ b/redis.c @@ -85,7 +85,6 @@ /* Hash table parameters */ #define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */ -#define REDIS_HT_MINSLOTS 16384 /* Never resize the HT under this */ /* Command flags */ #define REDIS_CMD_BULK 1 /* Bulk write command */ @@ -370,6 +369,7 @@ static void sremCommand(redisClient *c); static void smoveCommand(redisClient *c); static void sismemberCommand(redisClient *c); static void scardCommand(redisClient *c); +static void spopCommand(redisClient *c); static void sinterCommand(redisClient *c); static void sinterstoreCommand(redisClient *c); static void sunionCommand(redisClient *c); @@ -417,6 +417,7 @@ static struct redisCommand cmdTable[] = { {"smove",smoveCommand,4,REDIS_CMD_BULK}, {"sismember",sismemberCommand,3,REDIS_CMD_BULK}, {"scard",scardCommand,2,REDIS_CMD_INLINE}, + {"spop",spopCommand,2,REDIS_CMD_INLINE}, {"sinter",sinterCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, {"sinterstore",sinterstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, {"sunion",sunionCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, @@ -691,22 +692,28 @@ static void closeTimedoutClients(void) { } } +static int htNeedsResize(dict *dict) { + long long size, used; + + size = dictSlots(dict); + used = dictSize(dict); + return (size && used && size > DICT_HT_INITIAL_SIZE && + (used*100/size < REDIS_HT_MINFILL)); +} + /* If the percentage of used slots in the HT reaches REDIS_HT_MINFILL * we resize the hash table to save memory */ static void tryResizeHashTables(void) { int j; for (j = 0; j < server.dbnum; j++) { - long long size, used; - - size = dictSlots(server.db[j].dict); - used = dictSize(server.db[j].dict); - if (size && used && size > REDIS_HT_MINSLOTS && - (used*100/size < REDIS_HT_MINFILL)) { - redisLog(REDIS_NOTICE,"The hash table %d is too sparse, resize it...",j); + if (htNeedsResize(server.db[j].dict)) { + redisLog(REDIS_DEBUG,"The hash table %d is too sparse, resize it...",j); dictResize(server.db[j].dict); - redisLog(REDIS_NOTICE,"Hash table %d resized.",j); + redisLog(REDIS_DEBUG,"Hash table %d resized.",j); } + if (htNeedsResize(server.db[j].expires)) + dictResize(server.db[j].expires); } } @@ -2961,6 +2968,7 @@ static void sremCommand(redisClient *c) { } if (dictDelete(set->ptr,c->argv[2]) == DICT_OK) { server.dirty++; + if (htNeedsResize(set->ptr)) dictResize(set->ptr); addReply(c,shared.cone); } else { addReply(c,shared.czero); @@ -3040,6 +3048,34 @@ static void scardCommand(redisClient *c) { } } +static void spopCommand(redisClient *c) { + robj *set; + dictEntry *de; + + set = lookupKeyWrite(c->db,c->argv[1]); + if (set == NULL) { + addReply(c,shared.nullbulk); + } else { + if (set->type != REDIS_SET) { + addReply(c,shared.wrongtypeerr); + return; + } + de = dictGetRandomKey(set->ptr); + if (de == NULL) { + addReply(c,shared.nullbulk); + } else { + robj *ele = dictGetEntryKey(de); + + addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",sdslen(ele->ptr))); + addReply(c,ele); + addReply(c,shared.crlf); + dictDelete(set->ptr,ele); + if (htNeedsResize(set->ptr)) dictResize(set->ptr); + server.dirty++; + } + } +} + static int qsortCompareSetsByCardinality(const void *s1, const void *s2) { dict **d1 = (void*) s1, **d2 = (void*) s2; @@ -4170,6 +4206,7 @@ static struct redisFunctionSym symsTable[] = { {"smoveCommand", (unsigned long)smoveCommand}, {"sismemberCommand", (unsigned long)sismemberCommand}, {"scardCommand", (unsigned long)scardCommand}, +{"spopCommand", (unsigned long)spopCommand}, {"sinterCommand", (unsigned long)sinterCommand}, {"sinterstoreCommand", (unsigned long)sinterstoreCommand}, {"sunionCommand", (unsigned long)sunionCommand}, @@ -4296,6 +4333,9 @@ static void setupSigSegvAction(void) { act.sa_sigaction = segvHandler; sigaction (SIGSEGV, &act, NULL); sigaction (SIGBUS, &act, NULL); + sigaction (SIGFPE, &act, NULL); + sigaction (SIGILL, &act, NULL); + sigaction (SIGBUS, &act, NULL); return; } #else /* HAVE_BACKTRACE */ diff --git a/test-redis.tcl b/test-redis.tcl index bd58cb2e7..f5a03161a 100644 --- a/test-redis.tcl +++ b/test-redis.tcl @@ -515,6 +515,14 @@ proc main {server port} { lsort [$r smembers sres] } {1 2 3 4} + test {SPOP basics} { + $r del myset + $r sadd myset 1 + $r sadd myset 2 + $r sadd myset 3 + list [lsort [list [$r spop myset] [$r spop myset] [$r spop myset]]] [$r scard myset] + } {{1 2 3} 0} + test {SAVE - make sure there are all the types as values} { $r lpush mysavelist hello $r lpush mysavelist world