diff options
author | michael-grunder <michael.grunder@gmail.com> | 2014-08-01 16:18:36 -0700 |
---|---|---|
committer | Matt Stancliff <matt@genges.com> | 2015-01-05 11:21:38 -0500 |
commit | 40f7035ba47c3cdf161e3801d89fcc8afbd0c458 (patch) | |
tree | bbff5a29cf1f73f3b1581bfdba82301078320b81 | |
parent | 3315c098394252cbbe00c0a69ea159f345f4f8b1 (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
-rw-r--r-- | async.c | 6 | ||||
-rw-r--r-- | hiredis.c | 62 | ||||
-rw-r--r-- | hiredis.h | 2 | ||||
-rw-r--r-- | sds.c | 190 | ||||
-rw-r--r-- | sds.h | 1 |
5 files changed, 253 insertions, 8 deletions
@@ -654,12 +654,12 @@ int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata } int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { - char *cmd; + sds cmd; int len; int status; - len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); + len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); status = __redisAsyncCommand(ac,fn,privdata,cmd,len); - free(cmd); + sdsfree(cmd); return status; } @@ -934,6 +934,56 @@ int redisFormatCommand(char **target, const char *format, ...) { return len; } +/* Format a command according to the Redis protocol using an sds string and + * sdscatfmt for the processing of arguments. This function takes the + * number of arguments, an array with arguments and an array with their + * lengths. If the latter is set to NULL, strlen will be used to compute the + * argument lengths. + */ +int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv, + const size_t *argvlen) +{ + sds cmd; + unsigned long long totlen; + int j; + size_t len; + + /* Abort on a NULL target */ + if (target == NULL) + return -1; + + /* Calculate our total size */ + totlen = 1+intlen(argc)+2; + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + totlen += bulklen(len); + } + + /* Use an SDS string for command construction */ + cmd = sdsempty(); + if (cmd == NULL) + return -1; + + /* We already know how much storage we need */ + cmd = sdsMakeRoomFor(cmd, totlen); + if (cmd == NULL) + return -1; + + /* Construct command */ + cmd = sdscatfmt(cmd, "*%i\r\n", argc); + for (j=0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + cmd = sdscatfmt(cmd, "$%T\r\n", len); + cmd = sdscatlen(cmd, argv[j], len); + cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); + } + + assert(sdslen(cmd)==totlen); + + *target = cmd; + return totlen; +} + /* Format a command according to the Redis protocol. This function takes the * number of arguments, an array with arguments and an array with their * lengths. If the latter is set to NULL, strlen will be used to compute the @@ -945,6 +995,10 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz size_t len; int totlen, j; + /* Abort on a NULL target */ + if (target == NULL) + return -1; + /* Calculate number of bytes needed for the command */ totlen = 1+intlen(argc)+2; for (j = 0; j < argc; j++) { @@ -1301,21 +1355,21 @@ int redisAppendCommand(redisContext *c, const char *format, ...) { } int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { - char *cmd; + sds cmd; int len; - len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); + len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); if (len == -1) { __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); return REDIS_ERR; } if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { - free(cmd); + sdsfree(cmd); return REDIS_ERR; } - free(cmd); + sdsfree(cmd); return REDIS_OK; } @@ -34,6 +34,7 @@ #include <stdio.h> /* for size_t */ #include <stdarg.h> /* for va_list */ #include <sys/time.h> /* for struct timeval */ +#include "sds.h" /* for sds */ #define HIREDIS_MAJOR 0 #define HIREDIS_MINOR 11 @@ -161,6 +162,7 @@ void freeReplyObject(void *reply); int redisvFormatCommand(char **target, const char *format, va_list ap); int redisFormatCommand(char **target, const char *format, ...); int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); +int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen); /* Context for a connection to Redis */ typedef struct redisContext { @@ -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. * @@ -76,6 +76,7 @@ sds sdscatprintf(sds s, const char *fmt, ...) sds sdscatprintf(sds s, const char *fmt, ...); #endif +sds sdscatfmt(sds s, char const *fmt, ...); void sdstrim(sds s, const char *cset); void sdsrange(sds s, int start, int end); void sdsupdatelen(sds s); |