From 4f686555ce962e6632235d824512ea8fdeda003c Mon Sep 17 00:00:00 2001
From: antirez <antirez@gmail.com>
Date: Tue, 6 May 2014 15:39:14 +0200
Subject: [PATCH] Scripting: objects caching for Lua c->argv creation.

Reusing small objects when possible is a major speedup under certain
conditions, since it is able to avoid the malloc/free pattern that
otherwise is performed for every argument in the client command vector.
---
 src/scripting.c | 43 ++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 40 insertions(+), 3 deletions(-)

diff --git a/src/scripting.c b/src/scripting.c
index 918f61ea..401ebcb2 100644
--- a/src/scripting.c
+++ b/src/scripting.c
@@ -200,6 +200,8 @@ void luaSortArray(lua_State *lua) {
     lua_pop(lua,1);             /* Stack: array (sorted) */
 }
 
+#define LUA_CMD_OBJCACHE_SIZE 32
+#define LUA_CMD_OBJCACHE_MAX_LEN 64
 int luaRedisGenericCommand(lua_State *lua, int raise_error) {
     int j, argc = lua_gettop(lua);
     struct redisCommand *cmd;
@@ -209,6 +211,8 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
     /* Cached across calls. */
     static robj **argv = NULL;
     static int argv_size = 0;
+    static robj *cached_objects[LUA_CMD_OBJCACHE_SIZE];
+    static int cached_objects_len[LUA_CMD_OBJCACHE_SIZE];
 
     /* Require at least one argument */
     if (argc == 0) {
@@ -231,7 +235,20 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
 
         obj_s = (char*)lua_tolstring(lua,j+1,&obj_len);
         if (obj_s == NULL) break; /* Not a string. */
-        argv[j] = createStringObject(obj_s, obj_len);
+
+        /* Try to use a cached object. */
+        if (cached_objects[j] && cached_objects_len[j] >= obj_len) {
+            char *s = cached_objects[j]->ptr;
+            struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
+
+            argv[j] = cached_objects[j];
+            cached_objects[j] = NULL;
+            memcpy(s,obj_s,obj_len+1);
+            sh->free += sh->len - obj_len;
+            sh->len = obj_len;
+        } else {
+            argv[j] = createStringObject(obj_s, obj_len);
+        }
     }
     
     /* Check if one of the arguments passed by the Lua script
@@ -348,8 +365,28 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
 cleanup:
     /* Clean up. Command code may have changed argv/argc so we use the
      * argv/argc of the client instead of the local variables. */
-    for (j = 0; j < c->argc; j++)
-        decrRefCount(c->argv[j]);
+    for (j = 0; j < c->argc; j++) {
+        robj *o = c->argv[j];
+
+        /* Try to cache the object in the cached_objects array.
+         * The object must be small, SDS-encoded, and with refcount = 1
+         * (we must be the only owner) for us to cache it. */
+        if (j < LUA_CMD_OBJCACHE_SIZE &&
+            o->refcount == 1 &&
+            (o->encoding == REDIS_ENCODING_RAW ||
+             o->encoding == REDIS_ENCODING_EMBSTR) &&
+            sdslen(o->ptr) <= LUA_CMD_OBJCACHE_MAX_LEN)
+        {
+            struct sdshdr *sh = (void*)(((char*)(o->ptr))-(sizeof(struct sdshdr)));
+
+            if (cached_objects[j]) decrRefCount(cached_objects[j]);
+            cached_objects[j] = o;
+            cached_objects_len[j] = sh->free + sh->len;
+        } else {
+            decrRefCount(o);
+        }
+    }
+
     if (c->argv != argv) {
         zfree(c->argv);
         argv = NULL;