summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authormaohuazhu <34360891+neal-zhu@users.noreply.github.com>2020-08-28 09:22:35 -0500
committerGitHub <noreply@github.com>2020-08-28 17:22:35 +0300
commitee4a15aae08486077eb604d3d82e4f350bfcc041 (patch)
tree02b5ef1b4ab60530bf289d8a38839942f01adb57 /src
parentc01e94a4319c416c4c231ffbea9e778d52424e30 (diff)
downloadredis-ee4a15aae08486077eb604d3d82e4f350bfcc041.tar.gz
Optimize __ziplistCascadeUpdate algorithm (#6886)
The previous algorithm is of O(n^2) time complexity. It would have run through the ziplist entries one by one, each time doing a `realloc` and a `memmove` (moving the entire tail of the ziplist). The new algorithm is O(n), it runs over all the records once, computing the size of the `realloc` needed, then does one `realloc`, and run thought the records again doing many smaller `memmove`s, each time moving just one record. So this change reduces many reallocs, and moves each record just once. Co-authored-by: zhumaohua <zhumaohua@megvii.com> Co-authored-by: Oran Agra <oran@redislabs.com>
Diffstat (limited to 'src')
-rw-r--r--src/ziplist.c249
1 files changed, 202 insertions, 47 deletions
diff --git a/src/ziplist.c b/src/ziplist.c
index 13881c117..d4bdb4e78 100644
--- a/src/ziplist.c
+++ b/src/ziplist.c
@@ -614,62 +614,99 @@ unsigned char *ziplistResize(unsigned char *zl, unsigned int len) {
* The pointer "p" points to the first entry that does NOT need to be
* updated, i.e. consecutive fields MAY need an update. */
unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) {
- size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), rawlen, rawlensize;
- size_t offset, noffset, extra;
- unsigned char *np;
- zlentry cur, next;
-
+ zlentry cur;
+ size_t prevlen, prevlensize, prevoffset; /* Informat of the last changed entry. */
+ size_t firstentrylen; /* Used to handle insert at head. */
+ size_t rawlen, curlen = intrev32ifbe(ZIPLIST_BYTES(zl));
+ size_t extra = 0, cnt = 0, offset;
+ size_t delta = 4; /* Extra bytes needed to update a entry's prevlen (5-1). */
+ unsigned char *tail = zl + intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl));
+
+ /* Empty ziplist */
+ if (p[0] == ZIP_END) return zl;
+
+ zipEntry(p, &cur);
+ firstentrylen = prevlen = cur.headersize + cur.len;
+ prevlensize = zipStorePrevEntryLength(NULL, prevlen);
+ prevoffset = p - zl;
+ p += prevlen;
+
+ /* Iterate ziplist to find out how many extra bytes do we need to update it. */
while (p[0] != ZIP_END) {
zipEntry(p, &cur);
- rawlen = cur.headersize + cur.len;
- rawlensize = zipStorePrevEntryLength(NULL,rawlen);
-
- /* Abort if there is no next entry. */
- if (p[rawlen] == ZIP_END) break;
- zipEntry(p+rawlen, &next);
/* Abort when "prevlen" has not changed. */
- if (next.prevrawlen == rawlen) break;
-
- if (next.prevrawlensize < rawlensize) {
- /* The "prevlen" field of "next" needs more bytes to hold
- * the raw length of "cur". */
- offset = p-zl;
- extra = rawlensize-next.prevrawlensize;
- zl = ziplistResize(zl,curlen+extra);
- p = zl+offset;
-
- /* Current pointer and offset for next element. */
- np = p+rawlen;
- noffset = np-zl;
-
- /* Update tail offset when next element is not the tail element. */
- if ((zl+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) != np) {
- ZIPLIST_TAIL_OFFSET(zl) =
- intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+extra);
- }
+ if (cur.prevrawlen == prevlen) break;
- /* Move the tail to the back. */
- memmove(np+rawlensize,
- np+next.prevrawlensize,
- curlen-noffset-next.prevrawlensize-1);
- zipStorePrevEntryLength(np,rawlen);
-
- /* Advance the cursor */
- p += rawlen;
- curlen += extra;
- } else {
- if (next.prevrawlensize > rawlensize) {
- /* This would result in shrinking, which we want to avoid.
- * So, set "rawlen" in the available bytes. */
- zipStorePrevEntryLengthLarge(p+rawlen,rawlen);
+ /* Abort when entry's "prevlensize" is big enough. */
+ if (cur.prevrawlensize >= prevlensize) {
+ if (cur.prevrawlensize == prevlensize) {
+ zipStorePrevEntryLength(p, prevlen);
} else {
- zipStorePrevEntryLength(p+rawlen,rawlen);
+ /* This would result in shrinking, which we want to avoid.
+ * So, set "prevlen" in the available bytes. */
+ zipStorePrevEntryLengthLarge(p, prevlen);
}
-
- /* Stop here, as the raw length of "next" has not changed. */
break;
}
+
+ /* cur.prevrawlen means cur is the former head entry. */
+ assert(cur.prevrawlen == 0 || cur.prevrawlen + delta == prevlen);
+
+ /* Update prev entry's info and advance the cursor. */
+ rawlen = cur.headersize + cur.len;
+ prevlen = rawlen + delta;
+ prevlensize = zipStorePrevEntryLength(NULL, prevlen);
+ prevoffset = p - zl;
+ p += rawlen;
+ extra += delta;
+ cnt++;
+ }
+
+ /* Extra bytes is zero all update has been done(or no need to update). */
+ if (extra == 0) return zl;
+
+ /* Update tail offset after loop. */
+ if (tail == zl + prevoffset) {
+ /* When the the last entry we need to update is also the tail, update tail offset
+ * unless this is the only entry that was updated (so the tail offset didn't change). */
+ if (extra - delta != 0) {
+ ZIPLIST_TAIL_OFFSET(zl) =
+ intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+extra-delta);
+ }
+ } else {
+ /* Update the tail offset in cases where the last entry we updated is not the tail. */
+ ZIPLIST_TAIL_OFFSET(zl) =
+ intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+extra);
+ }
+
+ /* Now "p" points at the first unchanged byte in original ziplist,
+ * move data after that to new ziplist. */
+ offset = p - zl;
+ zl = ziplistResize(zl, curlen + extra);
+ p = zl + offset;
+ memmove(p + extra, p, curlen - offset - 1);
+ p += extra;
+
+ /* Iterate all entries that need to be updated tail to head. */
+ while (cnt) {
+ zipEntry(zl + prevoffset, &cur);
+ rawlen = cur.headersize + cur.len;
+ /* Move entry to tail and reset prevlen. */
+ memmove(p - (rawlen - cur.prevrawlensize),
+ zl + prevoffset + cur.prevrawlensize,
+ rawlen - cur.prevrawlensize);
+ p -= (rawlen + delta);
+ if (cur.prevrawlen == 0) {
+ /* "cur" is the previous head entry, update its prevlen with firstentrylen. */
+ zipStorePrevEntryLength(p, firstentrylen);
+ } else {
+ /* An entry's prevlen can only increment 4 bytes. */
+ zipStorePrevEntryLength(p, cur.prevrawlen+delta);
+ }
+ /* Foward to previous entry. */
+ prevoffset -= cur.prevrawlen;
+ cnt--;
}
return zl;
}
@@ -1379,6 +1416,30 @@ static void verify(unsigned char *zl, zlentry *e) {
}
}
+static unsigned char *insertHelper(unsigned char *zl, char ch, size_t len, unsigned char *pos) {
+ assert(len <= ZIP_BIG_PREVLEN);
+ unsigned char data[ZIP_BIG_PREVLEN] = {0};
+ memset(data, ch, len);
+ return ziplistInsert(zl, pos, data, len);
+}
+
+static int compareHelper(unsigned char *zl, char ch, size_t len, int index) {
+ assert(len <= ZIP_BIG_PREVLEN);
+ unsigned char data[ZIP_BIG_PREVLEN] = {0};
+ memset(data, ch, len);
+ unsigned char *p = ziplistIndex(zl, index);
+ assert(p != NULL);
+ return ziplistCompare(p, data, len);
+}
+
+static size_t strEntryBytesSmall(size_t slen) {
+ return slen + zipStorePrevEntryLength(NULL, 0) + zipStoreEntryEncoding(NULL, 0, slen);
+}
+
+static size_t strEntryBytesLarge(size_t slen) {
+ return slen + zipStorePrevEntryLength(NULL, ZIP_BIG_PREVLEN) + zipStoreEntryEncoding(NULL, 0, slen);
+}
+
int ziplistTest(int argc, char **argv) {
unsigned char *zl, *p;
unsigned char *entry;
@@ -1927,6 +1988,100 @@ int ziplistTest(int argc, char **argv) {
stress(ZIPLIST_TAIL,100000,16384,256);
}
+ printf("Stress __ziplistCascadeUpdate:\n");
+ {
+ char data[ZIP_BIG_PREVLEN];
+ zl = ziplistNew();
+ for (int i = 0; i < 100000; i++) {
+ zl = ziplistPush(zl, (unsigned char*)data, ZIP_BIG_PREVLEN-4, ZIPLIST_TAIL);
+ }
+ unsigned long long start = usec();
+ zl = ziplistPush(zl, (unsigned char*)data, ZIP_BIG_PREVLEN-3, ZIPLIST_HEAD);
+ printf("Done. usec=%lld\n\n", usec()-start);
+ zfree(zl);
+ }
+
+ printf("Edge cases of __ziplistCascadeUpdate:\n");
+ {
+ /* Inserting a entry with data length greater than ZIP_BIG_PREVLEN-4
+ * will leads to cascade update. */
+ size_t s1 = ZIP_BIG_PREVLEN-4, s2 = ZIP_BIG_PREVLEN-3;
+ zl = ziplistNew();
+
+ zlentry e[4] = {{.prevrawlensize = 0, .prevrawlen = 0, .lensize = 0,
+ .len = 0, .headersize = 0, .encoding = 0, .p = NULL}};
+
+ zl = insertHelper(zl, 'a', s1, ZIPLIST_ENTRY_HEAD(zl));
+ verify(zl, e);
+
+ assert(e[0].prevrawlensize == 1 && e[0].prevrawlen == 0);
+ assert(compareHelper(zl, 'a', s1, 0));
+ ziplistRepr(zl);
+
+ /* No expand. */
+ zl = insertHelper(zl, 'b', s1, ZIPLIST_ENTRY_HEAD(zl));
+ verify(zl, e);
+
+ assert(e[0].prevrawlensize == 1 && e[0].prevrawlen == 0);
+ assert(compareHelper(zl, 'b', s1, 0));
+
+ assert(e[1].prevrawlensize == 1 && e[1].prevrawlen == strEntryBytesSmall(s1));
+ assert(compareHelper(zl, 'a', s1, 1));
+
+ ziplistRepr(zl);
+
+ /* Expand(tail included). */
+ zl = insertHelper(zl, 'c', s2, ZIPLIST_ENTRY_HEAD(zl));
+ verify(zl, e);
+
+ assert(e[0].prevrawlensize == 1 && e[0].prevrawlen == 0);
+ assert(compareHelper(zl, 'c', s2, 0));
+
+ assert(e[1].prevrawlensize == 5 && e[1].prevrawlen == strEntryBytesSmall(s2));
+ assert(compareHelper(zl, 'b', s1, 1));
+
+ assert(e[2].prevrawlensize == 5 && e[2].prevrawlen == strEntryBytesLarge(s1));
+ assert(compareHelper(zl, 'a', s1, 2));
+
+ ziplistRepr(zl);
+
+ /* Expand(only previous head entry). */
+ zl = insertHelper(zl, 'd', s2, ZIPLIST_ENTRY_HEAD(zl));
+ verify(zl, e);
+
+ assert(e[0].prevrawlensize == 1 && e[0].prevrawlen == 0);
+ assert(compareHelper(zl, 'd', s2, 0));
+
+ assert(e[1].prevrawlensize == 5 && e[1].prevrawlen == strEntryBytesSmall(s2));
+ assert(compareHelper(zl, 'c', s2, 1));
+
+ assert(e[2].prevrawlensize == 5 && e[2].prevrawlen == strEntryBytesLarge(s2));
+ assert(compareHelper(zl, 'b', s1, 2));
+
+ assert(e[3].prevrawlensize == 5 && e[3].prevrawlen == strEntryBytesLarge(s1));
+ assert(compareHelper(zl, 'a', s1, 3));
+
+ ziplistRepr(zl);
+
+ /* Delete from mid. */
+ unsigned char *p = ziplistIndex(zl, 2);
+ zl = ziplistDelete(zl, &p);
+ verify(zl, e);
+
+ assert(e[0].prevrawlensize == 1 && e[0].prevrawlen == 0);
+ assert(compareHelper(zl, 'd', s2, 0));
+
+ assert(e[1].prevrawlensize == 5 && e[1].prevrawlen == strEntryBytesSmall(s2));
+ assert(compareHelper(zl, 'c', s2, 1));
+
+ assert(e[2].prevrawlensize == 5 && e[2].prevrawlen == strEntryBytesLarge(s2));
+ assert(compareHelper(zl, 'a', s1, 2));
+
+ ziplistRepr(zl);
+
+ zfree(zl);
+ }
+
return 0;
}
#endif