summaryrefslogtreecommitdiff
path: root/sds.c
diff options
context:
space:
mode:
authormichael-grunder <michael.grunder@gmail.com>2014-08-01 16:18:36 -0700
committerMatt Stancliff <matt@genges.com>2015-01-05 11:21:38 -0500
commit40f7035ba47c3cdf161e3801d89fcc8afbd0c458 (patch)
treebbff5a29cf1f73f3b1581bfdba82301078320b81 /sds.c
parent3315c098394252cbbe00c0a69ea159f345f4f8b1 (diff)
Improve redisAppendCommandArgv performance
OK, perhaps the second time is a charm. I forgot that I had hiredis forked from a long time ago, so the initial pull request was hosed. :) * Pulled in sdscatfmt() from Redis, and modified it to accept a size_t (%T) style format specifier. * Pulled in sdsll2str() and sdsull2str() from Redis (needed by sdscatfmt). * Added a new method, redisFormatSdsCommandArgv() which takes and sds* as the target, rather than char* (and uses sdscatfmt instead of sprintf for the construction). I get roughly the following improvement: Old: 1.044806 New: 0.481620 The benchmark code itself can be found here: https://gist.github.com/michael-grunder/c92ef31bb632b3d0ad81 Closes #260
Diffstat (limited to 'sds.c')
-rw-r--r--sds.c190
1 files changed, 189 insertions, 1 deletions
diff --git a/sds.c b/sds.c
index 65ff649..9d83636 100644
--- a/sds.c
+++ b/sds.c
@@ -289,7 +289,74 @@ sds sdscpy(sds s, const char *t) {
return sdscpylen(s, t, strlen(t));
}
-/* Like sdscatprintf() but gets va_list instead of being variadic. */
+/* 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 lenght 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
+ * an reversed string. */
+ v = (value < 0) ? -value : value;
+ 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--;
+ }
+ return l;
+}
+
+/* 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
+ * an 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--;
+ }
+ return l;
+}
+
+/* Like sdscatpritf() but gets va_list instead of being variadic. */
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
va_list cpy;
char *buf, *t;
@@ -338,6 +405,127 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
return t;
}
+/* This function is similar to sdscatprintf, but much faster as it does
+ * not rely on sprintf() family functions implemented by the libc that
+ * are often very slow. Moreover directly handling the sds string as
+ * new data is concatenated provides a performance improvement.
+ *
+ * However this function only handles an incompatible subset of printf-alike
+ * format specifiers:
+ *
+ * %s - C String
+ * %S - SDS string
+ * %i - signed int
+ * %I - 64 bit signed integer (long long, int64_t)
+ * %u - unsigned int
+ * %U - 64 bit unsigned integer (unsigned long long, uint64_t)
+ * %T - A size_t variable.
+ * %% - Verbatim "%" character.
+ */
+sds sdscatfmt(sds s, char const *fmt, ...) {
+ struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+ size_t initlen = sdslen(s);
+ const char *f = fmt;
+ int i;
+ va_list ap;
+
+ va_start(ap,fmt);
+ f = fmt; /* Next format specifier byte to process. */
+ i = initlen; /* Position of the next byte to write to dest str. */
+ while(*f) {
+ char next, *str;
+ int l;
+ long long num;
+ unsigned long long unum;
+
+ /* Make sure there is always space for at least 1 char. */
+ if (sh->free == 0) {
+ s = sdsMakeRoomFor(s,1);
+ sh = (void*) (s-(sizeof(struct sdshdr)));
+ }
+
+ switch(*f) {
+ case '%':
+ next = *(f+1);
+ f++;
+ switch(next) {
+ case 's':
+ case 'S':
+ str = va_arg(ap,char*);
+ l = (next == 's') ? strlen(str) : sdslen(str);
+ if (sh->free < l) {
+ s = sdsMakeRoomFor(s,l);
+ sh = (void*) (s-(sizeof(struct sdshdr)));
+ }
+ memcpy(s+i,str,l);
+ sh->len += l;
+ sh->free -= l;
+ i += l;
+ break;
+ case 'i':
+ case 'I':
+ if (next == 'i')
+ num = va_arg(ap,int);
+ else
+ num = va_arg(ap,long long);
+ {
+ char buf[SDS_LLSTR_SIZE];
+ l = sdsll2str(buf,num);
+ if (sh->free < l) {
+ s = sdsMakeRoomFor(s,l);
+ sh = (void*) (s-(sizeof(struct sdshdr)));
+ }
+ memcpy(s+i,buf,l);
+ sh->len += l;
+ sh->free -= l;
+ i += l;
+ }
+ break;
+ case 'u':
+ case 'U':
+ case 'T':
+ if (next == 'u')
+ unum = va_arg(ap,unsigned int);
+ else if(next == 'U')
+ unum = va_arg(ap,unsigned long long);
+ else
+ unum = (unsigned long long)va_arg(ap,size_t);
+ {
+ char buf[SDS_LLSTR_SIZE];
+ l = sdsull2str(buf,unum);
+ if (sh->free < l) {
+ s = sdsMakeRoomFor(s,l);
+ sh = (void*) (s-(sizeof(struct sdshdr)));
+ }
+ memcpy(s+i,buf,l);
+ sh->len += l;
+ sh->free -= l;
+ i += l;
+ }
+ break;
+ default: /* Handle %% and generally %<unknown>. */
+ s[i++] = next;
+ sh->len += 1;
+ sh->free -= 1;
+ break;
+ }
+ break;
+ default:
+ s[i++] = *f;
+ sh->len += 1;
+ sh->free -= 1;
+ break;
+ }
+ f++;
+ }
+ va_end(ap);
+
+ /* Add null-term */
+ s[i] = '\0';
+ return s;
+}
+
+
/* Remove the part of the string from left and from right composed just of
* contiguous characters found in 'cset', that is a null terminted C string.
*