diff --git a/dist/sds/sds.c b/dist/sds/sds.c index 349d27e31..df2e4bacc 100644 --- a/dist/sds/sds.c +++ b/dist/sds/sds.c @@ -388,46 +388,20 @@ sds sdsMakeRoomFor(sds s, size_t addlen) { * * After the call, the passed sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ -sds sdsRemoveFreeSpace(sds s) { - void *sh, *newsh; - char type, oldtype = s[-1] & SDS_TYPE_MASK; - int hdrlen, oldhdrlen = sdsHdrSize(oldtype); - size_t len = sdslen(s); - size_t avail = sdsavail(s); - sh = (char*)s-oldhdrlen; - - /* Return ASAP if there is no space left. */ - if (avail == 0) return s; - - /* Check what would be the minimum SDS header that is just good enough to - * fit this string. */ - type = sdsReqType(len); - hdrlen = sdsHdrSize(type); - - /* If the type is the same, or at least a large enough type is still - * required, we just realloc(), letting the allocator to do the copy - * only if really needed. Otherwise if the change is huge, we manually - * reallocate the string to use the different header type. */ - if (oldtype==type || type > SDS_TYPE_8) { - newsh = s_realloc(sh, oldhdrlen+len+1); - if (newsh == NULL) return NULL; - s = (char*)newsh+oldhdrlen; - } else { - newsh = s_malloc(hdrlen+len+1); - if (newsh == NULL) return NULL; - memcpy((char*)newsh+hdrlen, s, len+1); - s_free(sh); - s = (char*)newsh+hdrlen; - s[-1] = type; - sdssetlen(s, len); - } - sdssetalloc(s, len); - return s; +sds sdsRemoveFreeSpace(sds s, int would_regrow) { + return sdsResize(s, sdslen(s), would_regrow); } /* Resize the allocation, this can make the allocation bigger or smaller, - * if the size is smaller than currently used len, the data will be truncated */ -sds sdsResize(sds s, size_t size) { + * if the size is smaller than currently used len, the data will be truncated. + * + * The when the would_regrow argument is set to 1, it prevents the use of + * SDS_TYPE_5, which is desired when the sds is likely to be changed again. + * + * The sdsAlloc size will be set to the requested size regardless of the actual + * allocation size, this is done in order to avoid repeated calls to this + * function when the caller detects that it has excess space. */ +sds sdsResize(sds s, size_t size, int would_regrow) { void *sh, *newsh; char type, oldtype = s[-1] & SDS_TYPE_MASK; int hdrlen, oldhdrlen = sdsHdrSize(oldtype); @@ -443,8 +417,10 @@ sds sdsResize(sds s, size_t size) { /* Check what would be the minimum SDS header that is just good enough to * fit this string. */ type = sdsReqType(size); - /* Don't use type 5, it is not good for strings that are resized. */ - if (type == SDS_TYPE_5) type = SDS_TYPE_8; + if (would_regrow) { + /* Don't use type 5, it is not good for strings that are expected to grow back. */ + if (type == SDS_TYPE_5) type = SDS_TYPE_8; + } hdrlen = sdsHdrSize(type); /* If the type is the same, or can hold the size in it with low overhead @@ -452,12 +428,14 @@ sds sdsResize(sds s, size_t size) { * to do the copy only if really needed. Otherwise if the change is * huge, we manually reallocate the string to use the different header * type. */ - if (oldtype==type || (type < oldtype && type > SDS_TYPE_8)) { - newsh = s_realloc(sh, oldhdrlen+size+1); + int use_realloc = (oldtype==type || (type < oldtype && type > SDS_TYPE_8)); + size_t newlen = use_realloc ? oldhdrlen+size+1 : hdrlen+size+1; + if (use_realloc) { + newsh = s_realloc(sh, newlen); if (newsh == NULL) return NULL; s = (char*)newsh+oldhdrlen; } else { - newsh = s_malloc(hdrlen+size+1); + newsh = s_malloc(newlen); if (newsh == NULL) return NULL; memcpy((char*)newsh+hdrlen, s, len); s_free(sh); @@ -621,91 +599,123 @@ sds sdscpy(sds s, const char *t) { return sdscpylen(s, t, strlen(t)); } -/* Helper for sdscatlonglong() doing the actual number -> string - * conversion. 's' must point to a string with room for at least - * SDS_LLSTR_SIZE bytes. - * - * The function returns the length of the null-terminated string - * representation stored at 's'. */ -#define SDS_LLSTR_SIZE 21 -int sdsll2str(char *s, long long value) { - char *p, aux; - unsigned long long v; - size_t l; - - /* Generate the string representation, this method produces - * a reversed string. */ - if (value < 0) { - /* Since v is unsigned, if value==LLONG_MIN then - * -LLONG_MIN will overflow. */ - if (value != LLONG_MIN) { - v = -value; - } else { - v = ((unsigned long long)LLONG_MAX) + 1; +/* Return the number of digits of 'v' when converted to string in radix 10. + * See sdsll2string() for more information. */ +uint32_t digits10(uint64_t v) { + if (v < 10) return 1; + if (v < 100) return 2; + if (v < 1000) return 3; + if (v < 1000000000000UL) { + if (v < 100000000UL) { + if (v < 1000000) { + if (v < 10000) return 4; + return 5 + (v >= 100000); + } + return 7 + (v >= 10000000UL); } - } else { - v = value; + if (v < 10000000000UL) { + return 9 + (v >= 1000000000UL); + } + return 11 + (v >= 100000000000UL); + } + return 12 + digits10(v / 1000000000000UL); +} + +/* Convert a unsigned long long into a string. Returns the number of + * characters needed to represent the number. + * If the buffer is not big enough to store the string, 0 is returned. + * + * Based on the following article (that apparently does not provide a + * novel approach but only publicizes an already used technique): + * + * https://www.facebook.com/notes/facebook-engineering/three-optimization-tips-for-c/10151361643253920 */ +int sdsull2string(char *dst, size_t dstlen, unsigned long long value) { + static const char digits[201] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + + /* Check length. */ + uint32_t length = digits10(value); + if (length >= dstlen) goto err;; + + /* Null term. */ + uint32_t next = length - 1; + dst[next + 1] = '\0'; + while (value >= 100) { + int const i = (value % 100) * 2; + value /= 100; + dst[next] = digits[i + 1]; + dst[next - 1] = digits[i]; + next -= 2; } - p = s; - do { - *p++ = '0'+(v%10); - v /= 10; - } while(v); - if (value < 0) *p++ = '-'; - - /* Compute length and add null term. */ - l = p-s; - *p = '\0'; - - /* Reverse the string. */ - p--; - while(s < p) { - aux = *s; - *s = *p; - *p = aux; - s++; - p--; + /* Handle last 1-2 digits. */ + if (value < 10) { + dst[next] = '0' + (uint32_t) value; + } else { + int i = (uint32_t) value * 2; + dst[next] = digits[i + 1]; + dst[next - 1] = digits[i]; } - return l; + return length; +err: + /* force add Null termination */ + if (dstlen > 0) + dst[0] = '\0'; + return 0; } -/* Identical sdsll2str(), but for unsigned long long type. */ -int sdsull2str(char *s, unsigned long long v) { - char *p, aux; - size_t l; - - /* Generate the string representation, this method produces - * a reversed string. */ - p = s; - do { - *p++ = '0'+(v%10); - v /= 10; - } while(v); - - /* Compute length and add null term. */ - l = p-s; - *p = '\0'; - - /* Reverse the string. */ - p--; - while(s < p) { - aux = *s; - *s = *p; - *p = aux; - s++; - p--; +/* Convert a long long into a string. Returns the number of + * characters needed to represent the number. + * If the buffer is not big enough to store the string, 0 is returned. */ +int sdsll2string(char *dst, size_t dstlen, long long svalue) { + unsigned long long value; + int negative = 0; + + /* The ull2string function with 64bit unsigned integers for simplicity, so + * we convert the number here and remember if it is negative. */ + if (svalue < 0) { + if (svalue != LLONG_MIN) { + value = -svalue; + } else { + value = ((unsigned long long) LLONG_MAX)+1; + } + if (dstlen < 2) + goto err; + negative = 1; + dst[0] = '-'; + dst++; + dstlen--; + } else { + value = svalue; } - return l; + + /* Converts the unsigned long long value to string*/ + int length = sdsull2string(dst, dstlen, value); + if (length == 0) return 0; + return length + negative; + +err: + /* force add Null termination */ + if (dstlen > 0) + dst[0] = '\0'; + return 0; } + +/* Bytes needed for long -> str + '\0' */ +#define SDS_LLSTR_SIZE 21 + /* Create an sds string from a long long value. It is much faster than: * * sdscatprintf(sdsempty(),"%lld\n", value); */ sds sdsfromlonglong(long long value) { char buf[SDS_LLSTR_SIZE]; - int len = sdsll2str(buf,value); + int len = sdsll2string(buf,sizeof(buf),value); return sdsnewlen(buf,len); } @@ -853,7 +863,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) { num = va_arg(ap,long long); { char buf[SDS_LLSTR_SIZE]; - l = sdsll2str(buf,num); + l = sdsll2string(buf,sizeof(buf),num); if (sdsavail(s) < l) { s = sdsMakeRoomFor(s,l); } @@ -873,7 +883,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) { unum = va_arg(ap,unsigned long long); { char buf[SDS_LLSTR_SIZE]; - l = sdsull2str(buf,unum); + l = sdsull2string(buf,sizeof(buf),unum); if (sdsavail(s) < l) { s = sdsMakeRoomFor(s,l); } @@ -1565,27 +1575,27 @@ int sdsTest(void) { /* Test sdsresize - extend */ x = sdsnew("1234567890123456789012345678901234567890"); - x = sdsResize(x, 200); + x = sdsResize(x, 200, 1); test_cond("sdsrezie() expand len", sdslen(x) == 40); test_cond("sdsrezie() expand strlen", strlen(x) == 40); test_cond("sdsrezie() expand alloc", sdsalloc(x) == 200); /* Test sdsresize - trim free space */ - x = sdsResize(x, 80); + x = sdsResize(x, 80, 1); test_cond("sdsrezie() shrink len", sdslen(x) == 40); test_cond("sdsrezie() shrink strlen", strlen(x) == 40); test_cond("sdsrezie() shrink alloc", sdsalloc(x) == 80); /* Test sdsresize - crop used space */ - x = sdsResize(x, 30); + x = sdsResize(x, 30, 1); test_cond("sdsrezie() crop len", sdslen(x) == 30); test_cond("sdsrezie() crop strlen", strlen(x) == 30); test_cond("sdsrezie() crop alloc", sdsalloc(x) == 30); /* Test sdsresize - extend to different class */ - x = sdsResize(x, 400); + x = sdsResize(x, 400, 1); test_cond("sdsrezie() expand len", sdslen(x) == 30); test_cond("sdsrezie() expand strlen", strlen(x) == 30); test_cond("sdsrezie() expand alloc", sdsalloc(x) == 400); /* Test sdsresize - shrink to different class */ - x = sdsResize(x, 4); + x = sdsResize(x, 4, 1); test_cond("sdsrezie() crop len", sdslen(x) == 4); test_cond("sdsrezie() crop strlen", strlen(x) == 4); test_cond("sdsrezie() crop alloc", sdsalloc(x) == 4); diff --git a/dist/sds/sds.h b/dist/sds/sds.h index 65eb6e8eb..8aebe1c58 100644 --- a/dist/sds/sds.h +++ b/dist/sds/sds.h @@ -133,8 +133,8 @@ int sdsneedsrepr(const sds s); /* Low level functions exposed to the user API */ sds sdsMakeRoomFor(sds s, size_t addlen); void sdsIncrLen(sds s, ssize_t incr); -sds sdsRemoveFreeSpace(sds s); -sds sdsResize(sds s, size_t size); +sds sdsRemoveFreeSpace(sds s, int would_regrow); +sds sdsResize(sds s, size_t size, int would_regrow); size_t sdsAllocSize(sds s); void *sdsAllocPtr(sds s);