Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ruanhaishen
redis
Commits
05ba119f
Commit
05ba119f
authored
Jan 08, 2015
by
Salvatore Sanfilippo
Browse files
Merge pull request #2143 from mattsta/quicklist
Quicklist (linked list + ziplist)
parents
9e718a1f
5870e224
Changes
47
Hide whitespace changes
Inline
Side-by-side
deps/Makefile
View file @
05ba119f
...
@@ -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
...
...
redis.conf
View file @
05ba119f
...
@@ -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
...
...
src/Makefile
View file @
05ba119f
...
@@ -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
...
...
src/aof.c
View file @
05ba119f
...
@@ -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"
);
}
}
...
...
src/cluster.c
View file @
05ba119f
...
@@ -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
);
...
...
src/config.c
View file @
05ba119f
...
@@ -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
);
...
...
src/crc64.c
View file @
05ba119f
...
@@ -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
;
...
...
src/crc64.h
View file @
05ba119f
...
@@ -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
src/debug.c
View file @
05ba119f
...
@@ -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
);
...
...
src/endianconv.c
View file @
05ba119f
...
@@ -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 TEST
MAIN
#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
);
...
...
src/endianconv.h
View file @
05ba119f
...
@@ -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
src/intset.c
View file @
05ba119f
...
@@ -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
;
u
int
32_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
src/intset.h
View file @
05ba119f
...
@@ -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
src/lzfP.h
View file @
05ba119f
...
@@ -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 2
3
).
* For best compression, use 15 or 16 (or more, up to 2
2
).
*/
*/
#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
...
...
src/lzf_c.c
View file @
05ba119f
/*
/*
* Copyright (c) 2000-200
8
Marc Alexander Lehmann <schmorp@schmorp.de>
* Copyright (c) 2000-20
1
0 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 hash
ing
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
--
);
...
...
src/lzf_d.c
View file @
05ba119f
/*
/*
* Copyright (c) 2000-200
7
Marc Alexander Lehmann <schmorp@schmorp.de>
* Copyright (c) 2000-20
1
0 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
}
}
}
}
...
...
src/networking.c
View file @
05ba119f
...
@@ -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
)
{
...
...
src/object.c
View file @
05ba119f
...
@@ -180,11 +180,10 @@ robj *dupStringObject(robj *o) {
...
@@ -180,11 +180,10 @@ robj *dupStringObject(robj *o) {
}
}
}
}
robj
*
create
L
istObject
(
void
)
{
robj
*
create
Quickl
istObject
(
void
)
{
list
*
l
=
listCreate
();
quick
list
*
l
=
quick
listCreate
();
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_
LINKED
LIST
:
return
"
linked
list"
;
case
REDIS_ENCODING_
QUICK
LIST
:
return
"
quick
list"
;
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"
;
...
...
src/quicklist.c
0 → 100644
View file @
05ba119f
/* 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
(
"
\t
provisioning 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
(
"
\t
splitting 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
src/quicklist.h
0 → 100644
View file @
05ba119f
/* 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__ */
Prev
1
2
3
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment